Browse Source

feat: Added missing info to IAP transaction and product structures (#32602)

trop[bot] 3 years ago
parent
commit
c41628d3b5

+ 7 - 0
docs/api/structures/payment-discount.md

@@ -0,0 +1,7 @@
+# PaymentDiscount Object
+
+* `identifier` string - A string used to uniquely identify a discount offer for a product.
+* `keyIdentifier` string - A string that identifies the key used to generate the signature.
+* `nonce` string - A universally unique ID (UUID) value that you define.
+* `signature` string - A UTF-8 string representing the properties of a specific discount offer, cryptographically signed.
+* `timestamp` number - The date and time of the signature's creation in milliseconds, formatted in Unix epoch time.

+ 9 - 0
docs/api/structures/product-discount.md

@@ -0,0 +1,9 @@
+# ProductDiscount Object
+
+* `identifier` string - A string used to uniquely identify a discount offer for a product.
+* `type` number - The type of discount offer.
+* `price` number - The discount price of the product in the local currency.
+* `priceLocale` string - The locale used to format the discount price of the product.
+* `paymentMode` string - The payment mode for this product discount. Can be `payAsYouGo`, `payUpFront`, or `freeTrial`.
+* `numberOfPeriods` number - An integer that indicates the number of periods the product discount is available.
+* `subscriptionPeriod` [ProductSubscriptionPeriod](product-subscription-period.md) (optional) - An object that defines the period for the product discount.

+ 4 - 0
docs/api/structures/product-subscription-period.md

@@ -0,0 +1,4 @@
+# ProductSubscriptionPeriod Object
+
+* `numberOfUnits` number - The number of units per subscription period.
+* `unit` string - The increment of time that a subscription period is specified in. Can be `day`, `week`, `month`, `year`.

+ 7 - 0
docs/api/structures/product.md

@@ -8,4 +8,11 @@
 * `price` number - The cost of the product in the local currency.
 * `formattedPrice` string - The locale formatted price of the product.
 * `currencyCode` string - 3 character code presenting a product's currency based on the ISO 4217 standard.
+* `introductoryPrice` [ProductDiscount](product-discount.md) (optional) - The object containing introductory price information for the product.
+available for the product.
+* `discounts` [ProductDiscount](product-discount.md)[] - An array of discount offers
+* `subscriptionGroupIdentifier` string - The identifier of the subscription group to which the subscription belongs.
+* `subscriptionPeriod` [ProductSubscriptionPeriod](product-subscription-period.md) (optional) - The period details for products that are subscriptions.
 * `isDownloadable` boolean - A boolean value that indicates whether the App Store has downloadable content for this product. `true` if at least one file has been associated with the product.
+* `downloadContentVersion` string - A string that identifies the version of the content.
+* `downloadContentLengths` number[] - The total size of the content, in bytes.

+ 2 - 0
docs/api/structures/transaction.md

@@ -9,3 +9,5 @@
 * `payment` Object
   * `productIdentifier` string - The identifier of the purchased product.
   * `quantity` Integer  - The quantity purchased.
+  * `applicationUsername` string - An opaque identifier for the user’s account on your system.
+  * `paymentDiscount` [PaymentDiscount](payment-discount.md) (optional) - The details of the discount offer to apply to the payment.

+ 3 - 0
filenames.auto.gni

@@ -101,11 +101,14 @@ auto_filenames = {
     "docs/api/structures/new-window-web-contents-event.md",
     "docs/api/structures/notification-action.md",
     "docs/api/structures/notification-response.md",
+    "docs/api/structures/payment-discount.md",
     "docs/api/structures/point.md",
     "docs/api/structures/post-body.md",
     "docs/api/structures/printer-info.md",
     "docs/api/structures/process-memory-info.md",
     "docs/api/structures/process-metric.md",
+    "docs/api/structures/product-discount.md",
+    "docs/api/structures/product-subscription-period.md",
     "docs/api/structures/product.md",
     "docs/api/structures/protocol-request.md",
     "docs/api/structures/protocol-response-upload-data.md",

+ 66 - 4
shell/browser/api/electron_api_in_app_purchase.cc

@@ -15,6 +15,22 @@
 
 namespace gin {
 
+template <>
+struct Converter<in_app_purchase::PaymentDiscount> {
+  static v8::Local<v8::Value> ToV8(
+      v8::Isolate* isolate,
+      const in_app_purchase::PaymentDiscount& paymentDiscount) {
+    gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
+    dict.SetHidden("simple", true);
+    dict.Set("identifier", paymentDiscount.identifier);
+    dict.Set("keyIdentifier", paymentDiscount.keyIdentifier);
+    dict.Set("nonce", paymentDiscount.nonce);
+    dict.Set("signature", paymentDiscount.signature);
+    dict.Set("timestamp", paymentDiscount.timestamp);
+    return dict.GetHandle();
+  }
+};
+
 template <>
 struct Converter<in_app_purchase::Payment> {
   static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
@@ -23,6 +39,10 @@ struct Converter<in_app_purchase::Payment> {
     dict.SetHidden("simple", true);
     dict.Set("productIdentifier", payment.productIdentifier);
     dict.Set("quantity", payment.quantity);
+    dict.Set("applicationUsername", payment.applicationUsername);
+    if (payment.paymentDiscount.has_value()) {
+      dict.Set("paymentDiscount", payment.paymentDiscount.value());
+    }
     return dict.GetHandle();
   }
 };
@@ -45,6 +65,41 @@ struct Converter<in_app_purchase::Transaction> {
   }
 };
 
+template <>
+struct Converter<in_app_purchase::ProductSubscriptionPeriod> {
+  static v8::Local<v8::Value> ToV8(
+      v8::Isolate* isolate,
+      const in_app_purchase::ProductSubscriptionPeriod&
+          productSubscriptionPeriod) {
+    gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
+    dict.SetHidden("simple", true);
+    dict.Set("numberOfUnits", productSubscriptionPeriod.numberOfUnits);
+    dict.Set("unit", productSubscriptionPeriod.unit);
+    return dict.GetHandle();
+  }
+};
+
+template <>
+struct Converter<in_app_purchase::ProductDiscount> {
+  static v8::Local<v8::Value> ToV8(
+      v8::Isolate* isolate,
+      const in_app_purchase::ProductDiscount& productDiscount) {
+    gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
+    dict.SetHidden("simple", true);
+    dict.Set("identifier", productDiscount.identifier);
+    dict.Set("type", productDiscount.type);
+    dict.Set("price", productDiscount.price);
+    dict.Set("priceLocale", productDiscount.priceLocale);
+    dict.Set("paymentMode", productDiscount.paymentMode);
+    dict.Set("numberOfPeriods", productDiscount.numberOfPeriods);
+    if (productDiscount.subscriptionPeriod.has_value()) {
+      dict.Set("subscriptionPeriod",
+               productDiscount.subscriptionPeriod.value());
+    }
+    return dict.GetHandle();
+  }
+};
+
 template <>
 struct Converter<in_app_purchase::Product> {
   static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
@@ -54,18 +109,25 @@ struct Converter<in_app_purchase::Product> {
     dict.Set("productIdentifier", val.productIdentifier);
     dict.Set("localizedDescription", val.localizedDescription);
     dict.Set("localizedTitle", val.localizedTitle);
-    dict.Set("contentVersion", val.localizedTitle);
+    dict.Set("contentVersion", val.contentVersion);
     dict.Set("contentLengths", val.contentLengths);
 
     // Pricing Information
     dict.Set("price", val.price);
     dict.Set("formattedPrice", val.formattedPrice);
-
-    // Currency Information
     dict.Set("currencyCode", val.currencyCode);
-
+    if (val.introductoryPrice.has_value()) {
+      dict.Set("introductoryPrice", val.introductoryPrice.value());
+    }
+    dict.Set("discounts", val.discounts);
+    dict.Set("subscriptionGroupIdentifier", val.subscriptionGroupIdentifier);
+    if (val.subscriptionPeriod.has_value()) {
+      dict.Set("subscriptionPeriod", val.subscriptionPeriod.value());
+    }
     // Downloadable Content Information
     dict.Set("isDownloadable", val.isDownloadable);
+    dict.Set("downloadContentVersion", val.downloadContentVersion);
+    dict.Set("downloadContentLengths", val.downloadContentLengths);
 
     return dict.GetHandle();
   }

+ 19 - 0
shell/browser/mac/in_app_purchase_observer.h

@@ -10,6 +10,7 @@
 
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 #if defined(__OBJC__)
 @class InAppTransactionObserver;
@@ -21,9 +22,27 @@ namespace in_app_purchase {
 
 // --------------------------- Structures ---------------------------
 
+struct PaymentDiscount {
+  std::string identifier;
+  std::string keyIdentifier;
+  std::string nonce;
+  std::string signature;
+  int timestamp;
+
+  PaymentDiscount();
+  PaymentDiscount(const PaymentDiscount&);
+  ~PaymentDiscount();
+};
+
 struct Payment {
   std::string productIdentifier = "";
   int quantity = 1;
+  std::string applicationUsername;
+  absl::optional<PaymentDiscount> paymentDiscount;
+
+  Payment();
+  Payment(const Payment&);
+  ~Payment();
 };
 
 struct Transaction {

+ 39 - 0
shell/browser/mac/in_app_purchase_observer.mm

@@ -94,6 +94,25 @@ using InAppTransactionCallback = base::RepeatingCallback<void(
   return [dateFormatter stringFromDate:date];
 }
 
+/**
+ * Convert a SKPaymentDiscount object to a PaymentDiscount structure.
+ *
+ * @param paymentDiscount - The SKPaymentDiscount object to convert.
+ */
+- (in_app_purchase::PaymentDiscount)skPaymentDiscountToStruct:
+    (SKPaymentDiscount*)paymentDiscount API_AVAILABLE(macosx(10.14.4)) {
+  in_app_purchase::PaymentDiscount paymentDiscountStruct;
+
+  paymentDiscountStruct.identifier = [paymentDiscount.identifier UTF8String];
+  paymentDiscountStruct.keyIdentifier =
+      [paymentDiscount.keyIdentifier UTF8String];
+  paymentDiscountStruct.nonce = [[paymentDiscount.nonce UUIDString] UTF8String];
+  paymentDiscountStruct.signature = [paymentDiscount.signature UTF8String];
+  paymentDiscountStruct.timestamp = [paymentDiscount.timestamp intValue];
+
+  return paymentDiscountStruct;
+}
+
 /**
  * Convert a SKPayment object to a Payment structure.
  *
@@ -110,6 +129,18 @@ using InAppTransactionCallback = base::RepeatingCallback<void(
     paymentStruct.quantity = (int)payment.quantity;
   }
 
+  if (payment.applicationUsername != nil) {
+    paymentStruct.applicationUsername =
+        [payment.applicationUsername UTF8String];
+  }
+
+  if (@available(macOS 10.14.4, *)) {
+    if (payment.paymentDiscount != nil) {
+      paymentStruct.paymentDiscount =
+          [self skPaymentDiscountToStruct:payment.paymentDiscount];
+    }
+  }
+
   return paymentStruct;
 }
 
@@ -178,6 +209,14 @@ using InAppTransactionCallback = base::RepeatingCallback<void(
 
 namespace in_app_purchase {
 
+PaymentDiscount::PaymentDiscount() = default;
+PaymentDiscount::PaymentDiscount(const PaymentDiscount&) = default;
+PaymentDiscount::~PaymentDiscount() = default;
+
+Payment::Payment() = default;
+Payment::Payment(const Payment&) = default;
+Payment::~Payment() = default;
+
 Transaction::Transaction() = default;
 Transaction::Transaction(const Transaction&) = default;
 Transaction::~Transaction() = default;

+ 30 - 2
shell/browser/mac/in_app_purchase_product.h

@@ -9,11 +9,35 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace in_app_purchase {
 
 // --------------------------- Structures ---------------------------
 
+struct ProductSubscriptionPeriod {
+  int numberOfUnits;
+  std::string unit;
+
+  ProductSubscriptionPeriod(const ProductSubscriptionPeriod&);
+  ProductSubscriptionPeriod();
+  ~ProductSubscriptionPeriod();
+};
+
+struct ProductDiscount {
+  std::string identifier;
+  int type;
+  double price = 0.0;
+  std::string priceLocale;
+  std::string paymentMode;
+  int numberOfPeriods;
+  absl::optional<ProductSubscriptionPeriod> subscriptionPeriod;
+
+  ProductDiscount(const ProductDiscount&);
+  ProductDiscount();
+  ~ProductDiscount();
+};
+
 struct Product {
   // Product Identifier
   std::string productIdentifier;
@@ -27,12 +51,16 @@ struct Product {
   // Pricing Information
   double price = 0.0;
   std::string formattedPrice;
-
-  // Currency Information
   std::string currencyCode;
+  absl::optional<ProductDiscount> introductoryPrice;
+  std::vector<ProductDiscount> discounts;
+  std::string subscriptionGroupIdentifier;
+  absl::optional<ProductSubscriptionPeriod> subscriptionPeriod;
 
   // Downloadable Content Information
   bool isDownloadable = false;
+  std::string downloadContentVersion;
+  std::vector<uint32_t> downloadContentLengths;
 
   Product(const Product&);
   Product();

+ 137 - 0
shell/browser/mac/in_app_purchase_product.mm

@@ -106,6 +106,79 @@
   return [numberFormatter stringFromNumber:price];
 }
 
+/**
+ * Convert a SKProductSubscriptionPeriod object to a ProductSubscriptionPeriod
+ * structure.
+ *
+ * @param productProductSubscriptionPeriod - The SKProductSubscriptionPeriod
+ * object to convert.
+ */
+- (in_app_purchase::ProductSubscriptionPeriod)
+    skProductSubscriptionPeriodToStruct:
+        (SKProductSubscriptionPeriod*)productSubscriptionPeriod
+    API_AVAILABLE(macosx(10.13.2)) {
+  in_app_purchase::ProductSubscriptionPeriod productSubscriptionPeriodStruct;
+
+  productSubscriptionPeriodStruct.numberOfUnits =
+      (int)productSubscriptionPeriod.numberOfUnits;
+
+  if (productSubscriptionPeriod.unit == SKProductPeriodUnitDay) {
+    productSubscriptionPeriodStruct.unit = "day";
+  } else if (productSubscriptionPeriod.unit == SKProductPeriodUnitWeek) {
+    productSubscriptionPeriodStruct.unit = "week";
+  } else if (productSubscriptionPeriod.unit == SKProductPeriodUnitMonth) {
+    productSubscriptionPeriodStruct.unit = "month";
+  } else if (productSubscriptionPeriod.unit == SKProductPeriodUnitYear) {
+    productSubscriptionPeriodStruct.unit = "year";
+  }
+
+  return productSubscriptionPeriodStruct;
+}
+
+/**
+ * Convert a SKProductDiscount object to a ProductDiscount structure.
+ *
+ * @param productDiscount - The SKProductDiscount object to convert.
+ */
+- (in_app_purchase::ProductDiscount)skProductDiscountToStruct:
+    (SKProductDiscount*)productDiscount API_AVAILABLE(macosx(10.13.2)) {
+  in_app_purchase::ProductDiscount productDiscountStruct;
+
+  if (productDiscount.paymentMode == SKProductDiscountPaymentModePayAsYouGo) {
+    productDiscountStruct.paymentMode = "payAsYouGo";
+  } else if (productDiscount.paymentMode ==
+             SKProductDiscountPaymentModePayUpFront) {
+    productDiscountStruct.paymentMode = "payUpFront";
+  } else if (productDiscount.paymentMode ==
+             SKProductDiscountPaymentModeFreeTrial) {
+    productDiscountStruct.paymentMode = "freeTrial";
+  }
+
+  productDiscountStruct.numberOfPeriods = (int)productDiscount.numberOfPeriods;
+
+  if (productDiscount.priceLocale != nil) {
+    productDiscountStruct.priceLocale =
+        [[self formatPrice:productDiscount.price
+                 withLocal:productDiscount.priceLocale] UTF8String];
+  }
+
+  if (productDiscount.subscriptionPeriod != nil) {
+    productDiscountStruct.subscriptionPeriod = [self
+        skProductSubscriptionPeriodToStruct:productDiscount.subscriptionPeriod];
+  }
+
+  if (@available(macOS 10.14.4, *)) {
+    productDiscountStruct.type = (int)productDiscount.type;
+    if (productDiscount.identifier != nil) {
+      productDiscountStruct.identifier =
+          [productDiscount.identifier UTF8String];
+    }
+    productDiscountStruct.price = [productDiscount.price doubleValue];
+  }
+
+  return productDiscountStruct;
+}
+
 /**
  * Convert a skProduct object to Product structure.
  *
@@ -156,9 +229,64 @@
       }
     }
   }
+  if (@available(macOS 10.13.2, *)) {
+    if (product.introductoryPrice != nil) {
+      productStruct.introductoryPrice =
+          [self skProductDiscountToStruct:product.introductoryPrice];
+    }
+    if (product.subscriptionPeriod != nil) {
+      productStruct.subscriptionPeriod =
+          [self skProductSubscriptionPeriodToStruct:product.subscriptionPeriod];
+    }
+  }
+  if (@available(macOS 10.14, *)) {
+    if (product.subscriptionGroupIdentifier != nil) {
+      productStruct.subscriptionGroupIdentifier =
+          [product.subscriptionGroupIdentifier UTF8String];
+    }
+  }
+  if (@available(macOS 10.14.4, *)) {
+    if (product.discounts != nil) {
+      productStruct.discounts.reserve([product.discounts count]);
+
+      for (SKProductDiscount* discount in product.discounts) {
+        productStruct.discounts.push_back(
+            [self skProductDiscountToStruct:discount]);
+      }
+    }
+  }
 
   // Downloadable Content Information
   productStruct.isDownloadable = [product downloadable];
+  if (@available(macOS 10.14, *)) {
+    if (product.downloadContentVersion != nil) {
+      productStruct.downloadContentVersion =
+          [product.downloadContentVersion UTF8String];
+    }
+    if (product.downloadContentLengths != nil) {
+      productStruct.downloadContentLengths.reserve(
+          [product.downloadContentLengths count]);
+
+      for (NSNumber* contentLength in product.downloadContentLengths) {
+        productStruct.downloadContentLengths.push_back(
+            [contentLength longLongValue]);
+      }
+    }
+  } else {
+    if (product.contentVersion != nil) {
+      productStruct.downloadContentVersion =
+          [product.contentVersion UTF8String];
+    }
+    if (product.contentLengths != nil) {
+      productStruct.downloadContentLengths.reserve(
+          [product.contentLengths count]);
+
+      for (NSNumber* contentLength in product.contentLengths) {
+        productStruct.downloadContentLengths.push_back(
+            [contentLength longLongValue]);
+      }
+    }
+  }
 
   return productStruct;
 }
@@ -171,6 +299,15 @@
 
 namespace in_app_purchase {
 
+ProductSubscriptionPeriod::ProductSubscriptionPeriod(
+    const ProductSubscriptionPeriod&) = default;
+ProductSubscriptionPeriod::ProductSubscriptionPeriod() = default;
+ProductSubscriptionPeriod::~ProductSubscriptionPeriod() = default;
+
+ProductDiscount::ProductDiscount(const ProductDiscount&) = default;
+ProductDiscount::ProductDiscount() = default;
+ProductDiscount::~ProductDiscount() = default;
+
 Product::Product() = default;
 Product::Product(const Product&) = default;
 Product::~Product() = default;