Once you create application, you have to create some products to sell.
In general there are these types of products supported by Apple’s App Store:
Consumables.
Consumables are products that can be used (‘consumed’) once and then repurchased multiple times.
Non-consumables
Non-consumables are products that are purchased once, have no expiry date, and remain permanently available within your app.
Auto-renewal subscriptions
Auto-renewal subscriptions are products or services that you pay for on a recurring basis. Developers love these, as they guarantee a steady income stream.
Non auto-renewal subscriptions.
Non auto-renewal subscriptions are those that run for a fixed period of time, after which you can choose to manually renew. Often, these last longer and cost more than auto-renewal subscriptions.
To create products navigate to the newly created app, and locate “MONETIZATION” on the left side. From there you could select to create “in-app-purchases” or “Subscriptions”
Refer to Apple documentation of how to create products: In-App purchase
When testing in the sandbox, subscription durations are shortened to help simulate different billing periods quickly:
1 week subscription = 3 minutes in sandbox
1 month subscription = 5 minutes in sandbox
3 months subscription = 15 minutes in sandbox
6 months subscription = 30 minutes in sandbox
1 year subscription = 1 hour in sandbox
Creating Sandbox testing users
Ahh, yeah … you need test users so you won’t be charged with each test.
Create new Xcode project and change the bundle id to the one that you set up above in the app store connect. (Refer to Creating application bundle in App Store connect in this article.)
In-app purchase is not enabled by default. Let’s add it in Signing & Capabilities
Add mainStore.storekit file by going to files->new and look for StoreKit2.
– Name it mainStore.storekit
– Make sure that you select ‘synced‘ so it will pull data from the actual App Store and select your apple bundle id (this is how the StoreKit2 will know which items to pull)4. Let’s add another Swift class which will be our shared Store.
store.swift
import StoreKit
@MainActor final class Store: ObservableObject {
// use the same ids that you defined in the App Store connect
private var productIDs = ["InAppPurchaseTutorialToniconsumableFuel", "InAppPurchaseTutorialToniConsumableOil", "BrakePads", "InAppPurchaseRenewalPro"]
private var updates: Task<Void, Never>?
@Published var products = [Product]()
@Published var activeTransactions: Set<StoreKit.Transaction> = []
init() {
Task {
await requestProducts()
}
Task {
await transactionUpdates()
}
}
deinit {
updates?.cancel()
}
func transactionUpdates () async {
for await update in StoreKit.Transaction.updates {
if let transaction = try? update.payloadValue {
activeTransactions.insert(transaction)
await transaction.finish()
}
}
}
func requestProducts() async {
do {
products = try await Product.products(for: productIDs)
} catch {
print(error)
}
}
func purchaseProduct(_ product: Product) async throws {
print("Purchase tapped ...")
let result = try await product.purchase()
switch result {
case .success(let verifyResult):
print("Purchase successfull!")
if let transaction = try? verifyResult.payloadValue {
activeTransactions.insert(transaction)
print("PURCHASE:")
try print(verifyResult.payloadValue)
print(transaction)
await transaction.finish()
}
case .userCancelled:
print("Purchase Canceled !")
break
case .pending:
print("Purchase pending ...")
break
@unknown default:
break
}
}
func fetchActiveTransactions() async {
var activeTransactions: Set<StoreKit.Transaction> = []
for await entitelment in StoreKit.Transaction.currentEntitlements {
if let transaction = try? entitelment.payloadValue {
activeTransactions.insert(transaction)
print("fetchActiveTransactions: ")
print(transaction)
}
}
}
}
In productIDs use the same ids that you defined in the App Store connect.
5. Edit the View to add purchase buttons.
Views/ContentView.swift
//
// ContentView.swift
// InAppPurchaseTutorial
//
// Created by Toni Nichev on 1/2/24.
//
import SwiftUI
import StoreKit
struct ContentView: View {
@EnvironmentObject var store: Store
var body: some View {
VStack {
Text("Welcome to my store").font(.title)
ProductView(id: "InAppPurchaseTutorialToniconsumableFuel") {
Image(systemName: "crown")
}
.productViewStyle(.compact)
.padding()
.onInAppPurchaseCompletion { product, result in
if case .success(.success(let transaction)) = result {
print("Purchased successfully: \(transaction.signedDate)")
} else {
print("Something else happened")
}
}
}
Section(header: Text("To buy:").font(.title)) {
ForEach(store.products, id: \.id) { product in
Button {
Task {
try await store.purchaseProduct(product)
}
} label: {
HStack {
Text(product.displayName + ":")
Text(verbatim: product.displayPrice)
}
}
.buttonStyle(.borderedProminent)
}
}
}
}
#Preview {
ContentView().environmentObject(Store())
}
6. Last part is to add method to retreive purchases when the application starts.
InAppPurchaseTutorialApp.swift
//
// InAppPurchaseTutorialApp.swift
// InAppPurchaseTutorial
//
// Created by Toni Nichev on 1/2/24.
//
import SwiftUI
@main
struct InAppPurchaseTutorialApp: App {
@Environment(\.scenePhase) private var sceneParse
@StateObject private var store = Store()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(store)
.task(id: sceneParse) {
if sceneParse == .active {
await store.fetchActiveTransactions()
}
}
}
}
}
Setting up Backend server to listen to server-2-server notifications from Apple server
Apple server sends server-to-server notifications in real time when subscription event happens, like subscribed, cancel subscription, did renew etc.
Mode details of all notification types could be found in Apple documentation
Purpose: we can receive these notifications on transaction complete, on purchase made, on subscriptions: purchased, canceled, failed and we could grant access to subscribers to some paid services like (pro news, premium game levels etc.)
Copy the newly created PEM file to the root folder of the backend server handler, under ./assets folder.
Create your backend server handler, that Apple App Store server will notify.
I like to use PHP version cause it’s super easy to implement but any language works the same way:index.php
what we just did:
– decoded JWT
– Saved the decoded response in the data.txt file
so far so good, but we need to register our server-listener to the App-store connect so the Apple server will notify our server.
Now it’s a good time to testing with curl command and make sure that the script reads POST body data.
curl -X POST -H "Content-Type: application/json" -d '{"key":"value"}' https://yourserver.com/app-store-server-notification-tutorial/
After executing the command look at ./beep.txt file for the raw response. If you see it, you are ready to continue with a real test notification from Apple server.
Add your backend server url to Apps’ App Store Server notifications
If test notification is successful too, you could finally issue a real notification by doing test purchase from the app.
Now we could navigate to the physical phone, run the app and make a purchase.
Make sure that once prompted for the password you add the test account password.
Now we have server-2-server notification set up, but if we want to use more of Apple’s APIs (like transaction history, purchases, etc) we have to create an app to generate signed JWT.
Apple won’t accept JWT that lives longer than 20 min. so we have to make sure that this is the maximup ttl that we set up.
const expirationTime = now + 900; // Set to 15 minutes (900 seconds)
There are two types of Apple APIs like it was stated above:
<html>
<head>
<style>
h1 {
text-align: center;
}
#welcomePanel {
display: none;
text-align: center;
}
#signInPanel h2 {
text-align: center;
border: 1px solid silver;
}
#google-auth-button {
-webkit-box-align: baseline;
align-items: baseline;
border-width: 0px;
border-radius: 3px;
box-sizing: border-box;
display: inline-flex;
font-size: inherit;
font-style: normal;
font-family: inherit;
max-width: 100%;
position: relative;
text-align: center;
text-decoration: none;
transition: background 0.1s ease-out 0s, box-shadow 0.15s cubic-bezier(0.47, 0.03, 0.49, 1.38) 0s;
white-space: nowrap;
cursor: pointer;
padding: 0px 10px;
vertical-align: middle;
width: 100%;
-webkit-box-pack: center;
justify-content: center;
font-weight: bold;
color: var(--ds-text,#42526E) !important;
height: 40px !important;
line-height: 40px !important;
background: rgb(255, 255, 255) !important;
box-shadow: rgb(0 0 0 / 20%) 1px 1px 5px 0px !important;
}
</style>
<!-- First we have to include Apple Sign-in script -->
<script
type="text/javascript"
src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
</head>
<body>
<script>
function parseJwt (token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(window.atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
/**
* This function will initialize the `AppleID.auth` object with parameter we pass in.
*/
const initApple = () => {
window.AppleID.auth.init({
clientId: "com.toni-develops.sign-in-with-apple-identifier", // This is the service ID we created.
scope: "name email", // To tell apple we want the user name and emails fields in the response it sends us.
redirectURI: "https://www.toni-develops.com/external-files/examples/oauth-with-apple-using-js-library", // As registered along with our service ID
state: "origin:web", // Any string of your choice that you may use for some logic. It's optional and you may omit it.
usePopup: true, // Important if we want to capture the data apple sends on the client side.
});
};
/**
* This function is where the magic happens.
* This is a simple example, ideally you'll have catch block as well to handle authentication failure.
*/
const singInApple = async () => {
const response = await window.AppleID.auth.signIn();
return response;
};
initApple();
function signInWithApple() {
const userData = singInApple();
userData.then( (data) => {
console.dir(data, { depth: null });
const result = parseJwt(data.authorization.id_token)
console.dir(result, { depth: null });
document.querySelector('#signInPanel').innerHTML = '<h2>Welcome ' + result.email + '</h2>';
});
}
</script>
<h1>Sign-In with Apple using Java Script library example</h1>
<div id="signInPanel">
<button id="google-auth-button" class="css-11s2kpt" type="button" tabindex="0" onclick="signInWithApple()">
<span class="css-1ujqpe8">
<img class="appleLogo" src="https://www.toni-develops.com/external-files/examples/assets/apple-logo.svg" alt="">
</span>
<span class="css-19r5em7"><span>Continue with Apple</span>
</button>
</div>
</body>
</html>
Given two strings text1 and text2, return the length of their longest common subsequence. If there is no common subsequence, return 0.
A subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.
For example, "ace" is a subsequence of "abcde".
A common subsequence of two strings is a subsequence that is common to both strings.
Example 1:
Input:
text1 = "abcde", text2 = "ace"
Output:
3
Explanation:
The longest common subsequence is "ace" and its length is 3.
Example 2:
Input:
text1 = "abc", text2 = "abc"
Output:
3
Explanation:
The longest common subsequence is "abc" and its length is 3.
Example 3:
Input:
text1 = "abc", text2 = "def"
Output:
0
Explanation:
There is no such common subsequence, so the result is 0.
Constraints:
1 <= text1.length, text2.length <= 1000
text1 and text2 consist of only lowercase English characters.
This problem was taken from Leetcode Longest Common Subsequence
Solution
Dynamic programming with memoization.
We create a matrix to store (memoize) the response from previous calculations so we won’t re-calculate them again.
We are starting from the first row comparing every character from column 1 with the first character from row one.
There are two cases:
either the character match, then we add 1 and add it with the value from the diagonal to the left of the cell where we are.
Longest Common Substring Step 1
the characters don’t match, then we get the MAX of the value above current cell and the value to the left of the current cell.
Longest Common Substring Step 2
Here again c int the column (first string) matches c in the row (the second string)
so we get the value of last comparison (to the left and up diagonal of the current cell) which is 1 and add 1 again since characters match.
Longest Common Substring 3
and keep going till we reach the end of both strings which is the answer.
Longest Common Substring 4
/**
* @param {string} text1
* @param {string} text2
* @return {number}
*/
var longestCommonSubsequence = function(text1, text2) {
var txt1 = text1.split('');
var txt2 = text2.split('');
txt1.unshift('0');
txt2.unshift('1');
var l1 = txt1.length;
var l2 = txt2.length;
var matrix = [];
for(var i = 0; i < l2; i ++) {
matrix[i] = new Array(l1).fill(0);
}
var maxCommon = 0;
for(var row = 0; row < l2; row ++) {
for(var col = 0; col < l1; col ++) {
var last = 0;
if(txt1[col] == txt2[row]) {
var previousDiagonalRowVal = row == 0 || col == 0 ? 0 : matrix[row - 1][col - 1];
last = 1 + previousDiagonalRowVal;
}
else {
var prevUp = row == 0 ? 0 : matrix[row - 1][col];
var prevLeft = col == 0 ? 0 : matrix[row][col - 1];
last = Math.max(prevUp, prevLeft);
}
matrix[row][col] = last;
maxCommon = last > maxCommon ? last : maxCommon;
}
}
return maxCommon;
};
var text1 = "abcde", text2 = "ace";
var r = longestCommonSubsequence(text1, text2);
console.log(">>", r);
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
image was borrowed from leetcode
The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!
The brute force approach: for each element we go to the right and find the maximum height of the bar, then we go to the left and do the same.
For any element the maximum amount of the water that could be trapped will be the minimum of left height and right height, minus the height of the bar.
So for the array [0,1,0,2,1,0,1,3,2,1,2,1] we go all the way to the right and calculate the max right value, starting from first element ‘0’ max right will be 0. ‘1’ – max right is ‘1’ and so on.
We repeat the same from last element ‘1’ to the first one.
Then the trapped water for the first column will be: min(maxRight, maxLeft) – theArrayElement[n]
the array
0
1
0
2
1
0
1
3
2
1
2
1
max right
0
1
1
2
2
2
2
3
3
3
3
3
max left
3
3
3
3
3
3
3
3
2
2
2
1
collected
water
0
0
1
0
1
2
1
0
0
1
0
0
The complexity will be O(n2)
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
if(height.length < 2)
return 0;
let findMaxLeft = function(idx, height) {
let max = 0;
for(let i =idx;i >= 0; i --) {
max = Math.max(max, height[i]);
}
return max;
}
let findMaxRight = function(idx, height) {
let max = 0;
for(let i = idx;i < height.length; i ++) {
max = Math.max(max, height[i]);
}
return max;
}
let collectedWater = 0;
for(let i = 0;i < height.length; i ++) {
const maxLeft = findMaxLeft(i, height);
const maxRight = findMaxRight(i, height);
let min = Math.min(maxLeft, maxRight);
collectedWater += (min - height[i]);
}
return collectedWater;
};
The better solution: find all max left and max right with one loop, then do a second loop for each element in the array, and calculate trapped water.
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
let maxLeftArray = [], maxRightArray = [];
let maxLeft = 0, maxRight = 0;
const ln = height.length;
let trappedWater = 0;
for(let i = 0;i < height.length; i ++) {
maxLeftArray[i] = Math.max(height[i], maxLeft);
maxLeft = maxLeftArray[i];
maxRightArray[ln - i - 1] = Math.max(height[ln - i - 1], maxRight);
maxRight = maxRightArray[ln - i - 1];
}
for(let i = 0;i < height.length; i ++) {
trappedWater += Math.min(maxLeftArray[i], maxRightArray[i]) - height[i];
}
return trappedWater;
};
what we just did:
– With one loop find the max left and right bar on each side.
– for any element the maximum amount of the water that could be trapped will be the minimum of left height and right height, minus the height of the bar.
The brute force solution could be to make two loops and iterate through each elements in the array and repeatedly swapping the adjacent elements if they are in wrong order. This approach is called Bubble sort.
In the worst case scenario the complexity will be O(n*n) since we have to iterate n*n times where n is the number of elements in the array.
A better performing solution is to keep splitting the array into two halves till we have single elements. Then we start merging the arrays and sorting at the same time. This is called merge sort algorithm.
The diagram is borrowed from Wikipedia.
Merge sort algorithm implementation:
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortArray = function(nums) {
function mergeArray(nums, start, mid, end) {
var i = start, j = mid + 1;
var tempArr = [];
// compare till we reach either end of one of the halves
for(var k = start;(i < mid+1 && j < end+1); k++) {
if(nums[i] <= nums[j]) {
tempArr.push(nums[i]);
i++;
}
else {
tempArr.push(nums[j]);
j ++;
}
}
// add the rest from the first half
for(var k = j;k < end + 1; k++) {
tempArr.push(nums[k]);
}
// add the rest from the second half
for(var k = i;k < mid + 1; k++) {
tempArr.push(nums[k]);
}
// set up nums with sorted values
for(var k = start;k < end+1; k++) {
nums[k] = tempArr[k - start];
}
}
function mergeSort(nums, start, end) {
var mid = Math.floor((start + end) / 2);
if(start < end) {
mergeSort(nums, start, mid);
mergeSort(nums, mid + 1, end);
mergeArray(nums, start, mid, end);
}
}
mergeSort(nums, 0, nums.length - 1);
return nums;
}
var nums = [5,4,6,1,2,3];
var result = sortArray(nums);
console.log(result);
What we just did?
– started to split the array by half (line 39,40) which recursively calls mergeSort and keep splitting on smaller and smaller pieces till the array has only 1 element (line 37)
Let’s make the task more challenging and assume that we are going to have two different Brands (or apps)
one.localhost.com,
two.localhost.com,
for the most of the cases they will do the same thing but the styling will be different, so let’s add ability for our components to be styled in different ways, depending of the brand.
Passing the sub-domain to the PageLayout component
Let’s add new parameter to store the default brand.
what we just did: – (line 23) getting the sub domain, this time from the express request object, since this is happening on the server side.
– (line 30) passing extra parameter with the subDomain to the PageComponent
Now this will work fine for the production build, but if you run yarn start-api it will break with Invalid Host header message. This is a security feature and we need to tell Webpack dev server that we are going to have sub-domains.
This could be done by passing disableHostCheck: true and since we are using WebpackDevServer only for development this should be safe enough.
./server-api.js
import WebpackDevServer from 'webpack-dev-server';
import webpack from 'webpack';
import config from './webpack.api.config.js';
require('dotenv').config();
console.log(">>>" + process.env.GRAPHQL_URL);
const compiler = webpack(config);
const server = new WebpackDevServer(compiler, {
hot: true,
publicPath: config.output.publicPath,
historyApiFallback: true,
disableHostCheck: true,
});
server.listen(8080, 'localhost', function() {});
For the CLI config, if will look like this: --disable-host-check
This could be achieved in many different ways:
– we could either use redux and add the brand property there, exposing it to all connected components (which we will do later in this tutorial)
– or we could simply pass it as a property from the PageLayout component.
Let’s do the second one since it is straight forward.
Coming up with a good naming convention and folder structure is very important, but I will leave this to you to research.
I would propose the simplest one:
component
|- brands
| |_ one
| |- styles.scss
| |_ two
| |- styles.scss
|- index.js
./components/Home/index.js
import React from 'react';
const Home = ( {subDomain} ) => {
const styles = require(`./brands/${subDomain}/styles.scss`);
return (
<div>
<div className={styles.wrapper}>This is my home section!</div>
</div>
)
}
export default Home;
yarn start
hit one.localhost:3006/home and then two.localhost:3006/home and you will notice the difference right away!
But how this was achieved without re-rendering the component?
Let’s look at component’s css file (it might be 1.css or different number but look at the net tab and make sure that the CSS contains something like this):
– first brand has .one-wrapper{background:#8d8dac;color:#fff} and the second has two-wrapper{background:#fff;color:#000} and all CSS that is in common is added like this: .one-wrapper,.two-wrapper{text-align:center;font-family:MyFont} so no repeating CSS if it’s the same between brands.
Basically now the css file for this component will have all styling for both brands. This is nice but not perfect. We still load unnecessarily the styles for the brands that we are not in to. Let’s see how to fix this in the next chapter.
what we just did:
– we created a plain react component that will accept 3 parameters: – content [string]: rendered components returned from react-apollorenderToStringWithData(…) – cssBundles [array of CSS bundle objects]: – jsBundles [array of js bundle objects]:
– apolloClient [Object]: An instance of the Apollo client.
And now, let’s remove the HTML coder from ./server.js and add the new component instead, passing all necessary parameters.
./server-js
import React from 'react';
import express from 'express';
import App from './src/components/App/ssr-index';
import Loadable from 'react-loadable';
import manifest from './dist/loadable-manifest.json';
import { getDataFromTree } from "react-apollo";
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { renderToStringWithData } from "react-apollo"
import { createHttpLink } from 'apollo-link-http';
import { getBundles } from 'react-loadable/webpack';
import ReactDOMServer from 'react-dom/server';
import Html from './html.js';
const PORT = process.env.PROD_SERVER_PORT;
const app = express();
app.use('/server-build', express.static('./server-build'));
app.use('/dist', express.static('dist')); // to serve frontent prod static files
app.use('/favicon.ico', express.static('./src/images/favicon.ico'));
app.get('/*', (req, res) => {
const GRAPHQL_URL = process.env.GRAPHQL_URL;
const client = new ApolloClient({
ssrMode: true,
link: createHttpLink({
uri: GRAPHQL_URL,
fetch: fetch,
credentials: 'same-origin',
headers: {
cookie: req.header('Cookie'),
},
}),
cache: new InMemoryCache()
});
// Prepare to get list of all modules that have to be loaded for this route
const modules = [];
const mainApp = (
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<App req={req} client={client} />
</Loadable.Capture>
);
// Execute all queries and fetch the results before continue
getDataFromTree(mainApp).then(() => {
// Once we have the data back, this will render the components with the appropriate GraphQL data.
renderToStringWithData(<App req={req} client={client} />).then( (HTML_content) => {
// Extract CSS and JS bundles
const bundles = getBundles(manifest, modules);
const cssBundles = bundles.filter(bundle => bundle && bundle.file.split('.').pop() === 'css');
const jsBundles = bundles.filter(bundle => bundle && bundle.file.split('.').pop() === 'js');
const html = <Html content={HTML_content} cssBundles={cssBundles} jsBundles={jsBundles} apolloClient={client} />;
res.status(200);
res.send(`<!doctype html>\n${ReactDOMServer.renderToStaticMarkup(html)}`);
res.end();
});
}).catch( (error) => {
console.log("ERROR !!!!", error);
});
});
Loadable.preloadAll().then(() => {
app.listen(PORT, () => {
console.log(`? Server is listening on port ${PORT}`);
});
});
Starting with the first number 3 we assume that this is our smallest number since we don’t know the others.
Then we iterate through the whole array comparing 3 with other numbers.
If we find number that is smaller than 3 (in our case 1) this will become our smallest number, and we continue comparing it to the end of the array.
when we reach the end of the array we swap the first number, that we assumed that is the smallest one with the newly discovered smallest number (in our case number 1 at third position), and the array will look like this now: [1,15,3,5]
Now as we know for sure that the number at the first position (number 1) is the smallest one, we can start from position 2 and repeat steps 2 to 5 till we checked all numbers in the array.
function sortArray(list) {
var smallestIndex;
for(var i = 0; i < list.length; i ++) {
smallestIndex = i;
for(var ii = i + 1; ii < list.length;ii ++) {
smallestIndex = list[ii] < list[smallestIndex] ? ii : smallestIndex;
}
var larger = list[i];
list[i] = list[smallestIndex];
list[smallestIndex] = larger;
}
return list;
}
var listArray = [3,15,1,5];
var result = sortArray(listArray);
console.log(result);
Better approach:
Merge sort algorithm.
Let’s consider this example with adding more numbers: [3,15,1,5,4,9]