Skip to content

Commit

Permalink
Add documentation with Jazzy
Browse files Browse the repository at this point in the history
  • Loading branch information
iridescent-dev committed May 21, 2020
1 parent 5dbb33f commit 32b4f86
Show file tree
Hide file tree
Showing 168 changed files with 18,941 additions and 11,676 deletions.
52 changes: 52 additions & 0 deletions .jazzy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# `jazzy --help config`

output: docs
clean: true
author: Iridescent
author_url: https://iridescent.dev
module: InAppPurchaseLib
title: InAppPurchaseLib documentation
copyright: "Copyright © 2020 [Iridescent](https://iridescent.dev)."
documentation: Documentation/*/*.md
abstract: Documentation/*.md
github_url: https://github.com/iridescent-dev/iap-swift-lib
hide_documentation_coverage: false
custom_categories:
- name: "Getting Started"
children:
- "Installation"
- "Micro Example"
- "License"
- name: "Usage"
children:
- "Initialization"
- "Displaying products"
- "Displaying subscriptions"
- "Refreshing"
- "Purchasing"
- "Handling purchases"
- "Restoring purchases"
- "Displaying products with purchases"
- "Errors"
- "Analytics"
- "Server integration"
- name: "API documentation"
children:
- InAppPurchase
- InAppPurchaseLib
- DefaultPurchaseDelegate
- IAPPurchaseDelegate
- IAPProduct
- IAPProductType
- SKProduct
- IAPPeriodFormat
- IAPPurchaseCallback
- IAPRefreshCallback
- IAPPurchaseResult
- IAPRefreshResult
- IAPPurchaseResultState
- IAPRefreshResultState
- IAPError
- IAPErrorCode
- IAPErrorProtocol
theme: fullwidth
20 changes: 20 additions & 0 deletions Documentation/API documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Classes and Protocols

The most important class is `InAppPurchase`. All the functions you need are defined in this class.

If you have *consumable* and/or *non-renewing subscription* products in your application, you must have a class that adopts the `IAPPurchaseDelegate` protocol.

# Products
* Input: the library requires an array of `IAPProduct` when it is initialized.

* Output: the library will returns [SKProduct](https://developer.apple.com/documentation/storekit/skproduct) extended with helpful methods. See the [`SKProduct` extension](Extensions/SKProduct.html).

# Callbacks
`refresh()`, `purchase()` and `restorePurchases()` are asynchronous functions. You must provide a callback that will allow you to perform actions depending on the result.

* For `refresh()` and `restorePurchases()` functions, the result will be `IAPRefreshResult`.

* For `purchase()` function, the result will be `IAPPurchaseResult`.

# Errors
When calling `refresh()`, `purchase()` or `restorePurchases()`, the callback can return an `IAPError` if the state is `failed`. Look at `IAPErrorCode` to see the list of error codes you can receive.
10 changes: 10 additions & 0 deletions Documentation/Getting Started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
If you haven't already, I highly recommend your read the *Overview* and *Preparing* section of Apple's [In-App Purchase official documentation](https://developer.apple.com/in-app-purchase).

## Requirements
* Configure your App and Xcode to support In-App Purchases.
* [AppStore Connect Setup](https://help.apple.com/app-store-connect/#/devb57be10e7)
* Create and configure your [Fovea.Billing](https://billing.fovea.cc/?ref=iap-swift-lib) project account:
* Set your bundle ID
* The iOS Shared Secret (or shared key) is to be retrieved from [AppStoreConnect](https://appstoreconnect.apple.com/)
* The iOS Subscription Status URL (only if you want subscriptions)

14 changes: 14 additions & 0 deletions Documentation/Getting Started/Installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Installation
<p align="center">
<img src="https://github.com/iridescent-dev/iap-swift-lib/blob/master/Documentation/Images/ScreenshotInstallation.png" title="Installation">
</p>

* Select your project in Xcode
* Go to the section *Swift Package*
* Click on *(+) Add Package Dependency*
* Copy the Git URL: *https://github.com/iridescent-dev/iap-swift-lib.git*
* Click on *Next* > *Next*
* Make sure your project is selected in *Add to target*
* Click on *Finish*

*Note:* You have to `import InAppPurchaseLib` wherever you use the library.
90 changes: 90 additions & 0 deletions Documentation/Getting Started/Micro Example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Micro Example


```swift
/** AppDelegate.swift */
import UIKit
import InAppPurchaseLib

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Initialize the library
InAppPurchase.initialize(
iapProducts: [
IAPProduct(productIdentifier: "my_product", productType: .nonConsumable)
],
validatorUrlString: "https://validator.fovea.cc/v1/validate?appName=demo&apiKey=12345678"
)
return true
}

func applicationWillTerminate(_ application: UIApplication) {
// Clean
InAppPurchase.stop()
}
}
```

```swift
/** ViewController.swift */
import UIKit
import StoreKit
import InAppPurchaseLib

class ViewController: UIViewController {
private var loaderView = LoaderView()
@IBOutlet weak var statusLabel: UILabel!
@IBOutlet weak var productTitleLabel: UILabel!
@IBOutlet weak var productDescriptionLabel: UILabel!
@IBOutlet weak var purchaseButton: UIButton!
@IBOutlet weak var restorePurchasesButton: UIButton!

override func viewDidLoad() {
super.viewDidLoad()
// Add action for purchases and restore butons.
purchaseButton.addTarget(self, action: #selector(self.purchase), for: .touchUpInside)
restorePurchasesButton.addTarget(self, action: #selector(self.restorePurchases), for: .touchUpInside)
}

override func viewWillAppear(_ animated: Bool) {
self.refreshView()
InAppPurchase.refresh(callback: { _ in
self.refreshView()
})
}

func refreshView() {
guard let product: SKProduct = InAppPurchase.getProductBy(identifier: "my_product") else {
self.productTitleLabel.text = "Product unavailable"
return
}
// Display product information.
productTitleLabel.text = product.localizedTitle
productDescriptionLabel.text = product.localizedDescription
purchaseButton.setTitle(product.localizedPrice, for: .normal)

// Disable the button if the product has already been purchased.
if InAppPurchase.hasActivePurchase(for: "my_product") {
statusLabel.text = "OWNED"
purchaseButton.isPointerInteractionEnabled = false
}
}

@IBAction func purchase(_ sender: Any) {
self.loaderView.show()
InAppPurchase.purchase(
productIdentifier: "my_product",
callback: { result in
self.loaderView.hide()
})
}

@IBAction func restorePurchases(_ sender: Any) {
self.loaderView.show()
InAppPurchase.restorePurchases(callback: { result in
self.loaderView.hide()
})
}
}
```
File renamed without changes
File renamed without changes
21 changes: 21 additions & 0 deletions Documentation/License.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2020 Iridescent

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
8 changes: 8 additions & 0 deletions Documentation/Usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
The process of implementing in-app purchases involves several steps:
1. Displaying the list of purchasable products
2. Initiating a purchase
3. Delivering and finalizing a purchase
4. Checking the current ownership of non-consumables and subscriptions
5. Implementing the Restore Purchases button

*Note:* You have to `import InAppPurchaseLib` wherever you use the library.
42 changes: 42 additions & 0 deletions Documentation/Usage/Analytics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Analytics
Tracking the purchase flow is a common things in apps. Especially as it's core to your revenue model.

We can track 5 events, which step in the purchase pipeline a user reached.
1. `purchase initiated`
2. `purchase cancelled`
3. `purchase failed`
4. `purchase deferred`
5. `purchase succeeded`

Here's a quick example showing how to implement this correctly.

``` swift
func makePurchase() {
Analytics.trackEvent("purchase initiated")
InAppPurchase.purchase(
productIdentifier: "my_product_id",
callback: { result in
switch result.state {
case .purchased:
// Reminder: We are not processing the purchase here, only updating your UI.
// That's why we do not send an event to analytics.
case .failed:
Analytics.trackEvent("purchase failed")
case .deferred:
Analytics.trackEvent("purchase deferred")
case .cancelled:
Analytics.trackEvent("purchase cancelled")
}
})
}

// IAPPurchaseDelegate implementation
func productPurchased(productIdentifier: String) {
Analytics.trackEvent("purchase succeeded")
InAppPurchase.finishTransactions(for: productIdentifier)
}
```

The important part to remember is that a purchase can occur outside your app (or be approved when the app is not running), that's why tracking `purchase succeeded` has to be part of the `productPurchased` delegate function.

Refer to the [Consumables](handling-purchases.html#consumables) section to learn more about the `productPurchased` function.
59 changes: 59 additions & 0 deletions Documentation/Usage/Displaying products with purchases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Displaying products with purchases
In your store screen, where you present your products titles and prices with a purchase button, there are some cases to handle that we skipped. Owned products and deferred purchases.

### Owned products
Non-consumables and active auto-renewing subscriptions cannot be purchased again. You should adjust your UI to reflect that state. Refer to `InAppPurchase.hasActivePurchase()` to and to the example later in this section.

### Deferred purchases
Apple's **Ask to Buy** feature lets parents approve any purchases initiated by children, including in-app purchases.

With **Ask to Buy** enabled, when a child requests to make a purchase, the app is notified that the purchase is awaiting the parent’s approval in the purchase callback:

``` swift
InAppPurchase.purchase(
productIdentifier: productIdentifier,
callback: { result in
switch result.state {
case .deferred:
// Pending parent approval
}
})
```

In the _deferred_ case, the child has been notified by StoreKit that the parents have to approve the purchase. He might then close the app and come back later. You don't have much to do, but to display in your UI that there is a purchase waiting for parental approval in your views.

We will use the `hasDeferredTransaction` method:

``` swift
InAppPurchase.hasDeferredTransaction(for productIdentifier: String) -> Bool
```

### Example
Here's an example that covers what has been discussed above. We will update our example `refreshView` function from before:

``` swift
@objc func refreshView() {
guard let product: SKProduct = InAppPurchase.getProductBy(identifier: "my_product_id") else {
self.titleLabel.text = "Product unavailable"
return
}
self.titleLabel.text = product.localizedTitle
// ...

// "Ask to Buy" deferred purchase waiting for parent's approval
if InAppPurchase.hasDeferredTransaction(for: "my_product_id") {
self.statusLabel.text = "Waiting for Approval..."
self.purchaseButton.isPointerInteractionEnabled = false
}
// "Owned" product
else if InAppPurchase.hasActivePurchase(for: "my_product_id") {
self.statusLabel.text = "OWNED"
self.purchaseButton.isPointerInteractionEnabled = false
}
else {
self.purchaseButton.isPointerInteractionEnabled = true
}
}
```

When a product is owned or has a deferred purchase, we make sure the purchase button is grayed out. We also use a status label to display some details. Of course, you are free to design your UI as you see fit.
36 changes: 36 additions & 0 deletions Documentation/Usage/Displaying products.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Displaying products
Let's start with the simplest case: you have a single product.

You can retrieve all information about this product using the function `InAppPurchase.getProductBy(identifier: "my_product_id")`. This returns an [SKProduct](https://developer.apple.com/documentation/storekit/skproduct) extended with helpful methods.

Those are the most important:
- `productIdentifier: String` - The string that identifies the product to the Apple AppStore.
- `localizedTitle: String` - The name of the product, in the language of the device, as retrieved from the AppStore.
- `localizedDescription: String` - A description of the product, in the language of the device, as retrieved from the AppStore.
- `localizedPrice: String` - The cost of the product in the local currency (_read-only property added by this library_).

*Example*:

You can add a function similar to this to your view.

``` swift
@objc func refreshView() {
guard let product: SKProduct = InAppPurchase.getProductBy(identifier: "my_product_id") else {
self.titleLabel.text = "Product unavailable"
return
}
self.titleLabel.text = product.localizedTitle
self.descriptionLabel.text = product.localizedDescription
self.priceLabel.text = product.localizedPrice
}
```

This example assumes `self.titleLabel` is a UILabel, etc.

Make sure to call this function when the view appears on screen, for instance by calling it from [`viewWillAppear`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621510-viewwillappear).

``` swift
override func viewWillAppear(_ animated: Bool) {
self.refreshView()
}
```
36 changes: 36 additions & 0 deletions Documentation/Usage/Displaying subscriptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Displaying subscriptions
For subscription products, you also have some data about subscription periods and introductory offers.

- `func hasIntroductoryPriceEligible() -> Bool` - The product has an introductory price the user is eligible to.
- `localizedSubscriptionPeriod: String?` - The period of the subscription.
- `localizedIntroductoryPrice: String?` - The cost of the introductory offer if available in the local currency.
- `localizedIntroductoryPeriod: String?` - The subscription period of the introductory offer.
- `localizedIntroductoryDuration: String?` - The duration of the introductory offer.

**Example**

``` swift
@objc func refreshView() {
guard let product: SKProduct = InAppPurchase.getProductBy(identifier: "my_product_id") else {
self.titleLabel.text = "Product unavailable"
return
}
self.titleLabel.text = product.localizedTitle
self.descriptionLabel.text = product.localizedDescription

// Format price text. Example: "0,99€ / month for 3 months (then 3,99 € / month)"
var priceText = "\(product.localizedPrice) / \(product.localizedSubscriptionPeriod!)"
if product.hasIntroductoryPriceEligible() {
if product.introductoryPrice!.numberOfPeriods == 1 {
priceText = "\(product.localizedIntroductoryPrice!) for \(product.localizedIntroductoryDuration!)" +
" (then \(priceText))"
} else {
priceText = "\(product.localizedIntroductoryPrice!) / \(product.localizedIntroductoryPeriod!)" +
" for \(product.localizedIntroductoryDuration!) (then \(priceText))"
}
}
self.priceLabel.text = priceText
}
```

*Note:* You have to `import StoreKit` wherever you use `SKProduct`.
Loading

0 comments on commit 32b4f86

Please sign in to comment.