<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Shivam Maggu]]></title><description><![CDATA[iOS developer specializing in Swift and anything tech that piques my interest.]]></description><link>https://blog.shivammaggu.com</link><generator>RSS for Node</generator><lastBuildDate>Sat, 11 Apr 2026 21:25:29 GMT</lastBuildDate><atom:link href="https://blog.shivammaggu.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Working with UserDefaults in iOS]]></title><description><![CDATA[Features

The UserDefaults class provides access to the user's defaults database.

It stores data persistently across the app launches as long as the app is installed.

Access to the data is private to the app unless shared through App Groups.

Acces...]]></description><link>https://blog.shivammaggu.com/working-with-userdefaults-in-ios</link><guid isPermaLink="true">https://blog.shivammaggu.com/working-with-userdefaults-in-ios</guid><category><![CDATA[property wrapper]]></category><category><![CDATA[Swift]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[storage]]></category><category><![CDATA[UserDefaults]]></category><category><![CDATA[iOS]]></category><category><![CDATA[ios app development]]></category><category><![CDATA[Databases]]></category><dc:creator><![CDATA[Shivam Maggu]]></dc:creator><pubDate>Fri, 22 Dec 2023 12:20:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1703247462436/c5d988d9-a989-47c9-8786-672d46cdcb87.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-features">Features</h2>
<ul>
<li><p>The UserDefaults class provides access to the user's defaults database.</p>
</li>
<li><p>It stores data persistently across the app launches as long as the app is installed.</p>
</li>
<li><p>Access to the data is private to the app unless shared through App Groups.</p>
</li>
<li><p>Access to this database is thread-safe.</p>
</li>
<li><p>It is a key-value store that works through a property list (plist) file.</p>
</li>
<li><p>Supported data types include String, Bool, Date, Array, Dictionary, Data and Numbers.</p>
</li>
<li><p>Custom objects can be stored by encoding them as data.</p>
</li>
<li><p>We can use this to store non-sensitive data such as user preferences.</p>
</li>
</ul>
<h2 id="heading-usage">Usage</h2>
<h3 id="heading-utilising-the-default-shared-instance-of-userdefaults">Utilising the default shared instance of UserDefaults.</h3>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> Foundation
<span class="hljs-comment">// Set a key value in UserDefaults</span>
<span class="hljs-type">UserDefaults</span>.standard.<span class="hljs-keyword">set</span>(<span class="hljs-string">"Shivam"</span>, forKey: <span class="hljs-string">"firstname"</span>)
<span class="hljs-comment">// Get a key value from User Defaults</span>
<span class="hljs-type">UserDefaults</span>.standard.value(forKey: <span class="hljs-string">"firstname"</span>)
<span class="hljs-comment">// Empty User Defaults</span>
<span class="hljs-type">UserDefaults</span>.standard.removeObject(forKey: <span class="hljs-string">"firstname"</span>)
</code></pre>
<h3 id="heading-creating-a-userdefaults-that-can-be-shared-with-multiple-apps-and-extensions">Creating a UserDefaults that can be shared with multiple apps and extensions.</h3>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> Foundation
<span class="hljs-comment">// User Defaults with Suite.</span>
<span class="hljs-comment">// Useful for sharing data with other apps</span>
<span class="hljs-comment">// or extensions with app groups.</span>
<span class="hljs-keyword">let</span> defaults = <span class="hljs-type">UserDefaults</span>(suiteName: <span class="hljs-string">"group.com.organisation.appname"</span>)! 
<span class="hljs-comment">// Set a key value in User Defaults</span>
defaults.<span class="hljs-keyword">set</span>(<span class="hljs-string">"Shivam"</span>, forKey: <span class="hljs-string">"firstname"</span>) 
<span class="hljs-comment">// Get a key value from User Defaults</span>
defaults.value(forKey: <span class="hljs-string">"firstname"</span>) 
<span class="hljs-comment">// Empty User Defaults</span>
defaults.removeObject(forKey: <span class="hljs-string">"firstname"</span>)
</code></pre>
<h3 id="heading-storing-custom-objects-in-userdefaults">Storing custom objects in UserDefaults.</h3>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> Foundation
<span class="hljs-comment">// Custom model to be stored in UserDefaults</span>
<span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Student</span>: <span class="hljs-title">Codable</span> </span>{
    <span class="hljs-keyword">let</span> firstname: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> lastname: <span class="hljs-type">String</span>
    <span class="hljs-keyword">let</span> grade: <span class="hljs-type">Int</span>
    <span class="hljs-keyword">let</span> subjects: <span class="hljs-type">Array</span>&lt;<span class="hljs-type">String</span>&gt;
    <span class="hljs-keyword">let</span> teachers: [<span class="hljs-type">String</span>: <span class="hljs-type">String</span>]
    <span class="hljs-keyword">let</span> profileImage: <span class="hljs-type">Data</span>
    <span class="hljs-keyword">let</span> dateModified: <span class="hljs-type">Date</span>
}

<span class="hljs-comment">// Student Object</span>
<span class="hljs-keyword">let</span> student = <span class="hljs-type">Student</span>(firstname: <span class="hljs-string">"John"</span>,
                      lastname: <span class="hljs-string">"Doe"</span>,
                      grade: <span class="hljs-number">2</span>,
                      subjects: [<span class="hljs-string">"Hindi"</span>, <span class="hljs-string">"English"</span>, <span class="hljs-string">"Maths"</span>],
                      teachers: [<span class="hljs-string">"Hindi"</span>: <span class="hljs-string">"Teacher A"</span>, <span class="hljs-string">"English"</span>: <span class="hljs-string">"Teacher b"</span>, <span class="hljs-string">"Maths"</span>: <span class="hljs-string">"Teacher X"</span>],
                      profileImage: <span class="hljs-type">Data</span>(),
                      dateModified: <span class="hljs-type">Date</span>())

<span class="hljs-comment">// Encode custom object to data and store it in UserDefaults</span>
<span class="hljs-keyword">let</span> encoder = <span class="hljs-type">JSONEncoder</span>()
<span class="hljs-keyword">do</span> {
    <span class="hljs-keyword">let</span> data = <span class="hljs-keyword">try</span> encoder.encode(student)
    <span class="hljs-type">UserDefaults</span>.standard.setValue(data, forKey: <span class="hljs-string">"StudentData"</span>)
} <span class="hljs-keyword">catch</span> {
    <span class="hljs-keyword">throw</span> error
}

<span class="hljs-comment">// Retrieve data from UserDefaults and decode it to custom object</span>
<span class="hljs-keyword">let</span> decoder = <span class="hljs-type">JSONDecoder</span>()
<span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> data = <span class="hljs-type">UserDefaults</span>.standard.value(forKey: <span class="hljs-string">"StudentData"</span>) <span class="hljs-keyword">as</span>? <span class="hljs-type">Data</span> {
    <span class="hljs-keyword">do</span> {
        <span class="hljs-keyword">let</span> object = <span class="hljs-keyword">try</span> decoder.decode(<span class="hljs-type">Student</span>.<span class="hljs-keyword">self</span>, from: data)
        <span class="hljs-built_in">print</span>(object)
    } <span class="hljs-keyword">catch</span> {
        <span class="hljs-keyword">throw</span> error
    }
} <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"No data in UserDefaults."</span>)
}
</code></pre>
<h3 id="heading-using-userdefaults-as-a-property-wrapper">Using UserDefaults as a Property Wrapper</h3>
<p>Check out this <a target="_blank" href="https://gist.github.com/shivammaggu/451e06aafc8b25ba8ca9479a8f9ab175">gist to use UserDefaults with Property Wrapper</a>. This is a more generalized way to use in Projects.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="451e06aafc8b25ba8ca9479a8f9ab175"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/shivammaggu/451e06aafc8b25ba8ca9479a8f9ab175" class="embed-card">https://gist.github.com/shivammaggu/451e06aafc8b25ba8ca9479a8f9ab175</a></div>]]></content:encoded></item><item><title><![CDATA[Offline Persistent Storage in iOS Applications]]></title><description><![CDATA[What is offline storage?
It is a kind of storage not on a network but on a physical device. In regards to an iOS application, it means that the data generated or displayed by an app is not stored on or retrieved from a network. Instead, it resides lo...]]></description><link>https://blog.shivammaggu.com/offline-persistent-storage-in-ios-applications</link><guid isPermaLink="true">https://blog.shivammaggu.com/offline-persistent-storage-in-ios-applications</guid><category><![CDATA[persistant storage]]></category><category><![CDATA[Swift]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[CoreData]]></category><category><![CDATA[UserDefaults]]></category><category><![CDATA[Apple]]></category><category><![CDATA[offline]]></category><category><![CDATA[keychain]]></category><category><![CDATA[Databases]]></category><category><![CDATA[filemanager]]></category><category><![CDATA[Plist]]></category><dc:creator><![CDATA[Shivam Maggu]]></dc:creator><pubDate>Thu, 21 Dec 2023 18:35:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1703179956807/fbf2a3a7-282b-4d0b-baec-702c76b119b9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-offline-storage">What is offline storage?</h2>
<p>It is a kind of storage not on a network but on a physical device. In regards to an iOS application, it means that the data generated or displayed by an app is not stored on or retrieved from a network. Instead, it resides locally in the user's device.</p>
<h2 id="heading-why-do-we-need-it">Why do we need it?</h2>
<p>It makes the app more accessible to the users. They are not dependent on the internet to use the app's functionality.</p>
<h2 id="heading-when-should-we-use-it">When should we use it?</h2>
<p>Let's take examples of some popular apps that do not hinder user functionality when offline.</p>
<ul>
<li><p>Chat apps such as Telegram and WhatsApp can store your messages when offline and deliver them when the connection is restored.</p>
</li>
<li><p>Automation apps like Automate store users flow offline.</p>
</li>
<li><p>Offline dictionaries store the words and their definitions on the device.</p>
</li>
<li><p>E-book reading apps store the books offline so users can read anytime and anywhere.</p>
</li>
<li><p>Mail apps like Gmail and Outlook queue your recently sent emails in Outbox and send them once online.</p>
</li>
<li><p>Contacts and SMS apps store phone numbers and text messages.</p>
</li>
<li><p>OTT apps provide download features to store your favourite TV Shows and Movies locally and watch offline.</p>
</li>
<li><p>Todo lists, reminders, calendars, sketching and fitness tracker apps store data offline.</p>
</li>
<li><p>YouTube and Spotify can download our favourite songs to local storage.</p>
</li>
</ul>
<p>We should use offline persistent storage when an app calls for such use cases. We can also use offline storage to cache data models fetched from the REST APIs.</p>
<h2 id="heading-how-should-we-use-it">How should we use it?</h2>
<p>If your app architecture calls for offline storage there are various ways to achieve this in iOS. Mentioned below are some examples provided directly by Apple.</p>
<ul>
<li><p>UserDefaults</p>
</li>
<li><p>KeyChain</p>
</li>
<li><p>Property List</p>
</li>
<li><p>FileManager</p>
</li>
<li><p>URLCache</p>
</li>
<li><p>Core Data</p>
</li>
</ul>
<p>I'll be adding a separate article discussing the features, implementation and use case for each storage technique.</p>
]]></content:encoded></item><item><title><![CDATA[Understanding SwiftUI - Why are Views in SwiftUI Struct and not Class?]]></title><description><![CDATA[Why SwiftUI prefers Struct over Class?

SwiftUI Views are structs instead of classes. This avoids having multiple inherited properties, keeps them lightweight and improves performance.

SwiftUI Views are passed by value. This avoids retain cycles and...]]></description><link>https://blog.shivammaggu.com/understanding-swiftui-why-are-views-in-swiftui-struct-and-not-class</link><guid isPermaLink="true">https://blog.shivammaggu.com/understanding-swiftui-why-are-views-in-swiftui-struct-and-not-class</guid><category><![CDATA[Swift]]></category><category><![CDATA[SwiftUI]]></category><category><![CDATA[struct]]></category><category><![CDATA[class]]></category><category><![CDATA[source of truth]]></category><dc:creator><![CDATA[Shivam Maggu]]></dc:creator><pubDate>Sun, 26 Feb 2023 00:52:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677191506004/a9322781-bafc-4882-888c-e15664bacd25.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-why-swiftui-prefers-struct-over-class">Why SwiftUI prefers Struct over Class?</h1>
<ul>
<li><p>SwiftUI Views are structs instead of classes. This avoids having multiple inherited properties, keeps them lightweight and improves performance.</p>
</li>
<li><p>SwiftUI Views are passed by value. This avoids retain cycles and reference counting, leading to fewer memory leaks.</p>
</li>
<li><p>SwiftUI is based on the core principle of maintaining a <strong>Single Source of Truth.</strong></p>
<ul>
<li><p>View rendering depends on properties like the <code>@State</code>, <code>@ObservableObject</code> and so on. These are known as a Source of truth.</p>
</li>
<li><p>When the value of these properties changes, the SwiftUI framework asks the view body to render the UI with the updated values.</p>
</li>
<li><p>With UIKit, developers manage not only the state of the data but also the view changes based on that data. Whenever a view reads a piece of data, it creates a dependency between them. On data change, views should be updated to reflect the new value. When it fails to do so, then it becomes a bug. There can be multiple states in our app and multiple functions reacting to those state changes. If we do not maintain a source of truth for the current app state, UI bugs tend to creep in due to human error.</p>
</li>
<li><p>SwiftUI tends to overcome this issue by making <code>body</code> the only entry point. Despite the numerous states an app might find itself in, there is only one possible way to update the UI which is by rendering the view body. Views react to state changes by informing the framework. The framework then recomputes the derived values and generates the body with updated derived values.</p>
</li>
</ul>
</li>
</ul>
<h1 id="heading-swiftui-views-vs-uikit-views">SwiftUI Views vs. UIKit Views</h1>
<table><tbody><tr><td><p><strong>Serial No.</strong></p></td><td><p><strong>SwiftUI Views</strong></p></td><td><p><strong>UIKit Views</strong></p></td></tr><tr><td><p>1.</p></td><td><p>Views alone are the fundamental building blocks.</p></td><td><p>Views and Controllers work together to form the building blocks.</p></td></tr><tr><td><p>2.</p></td><td><p>Views are data-driven.</p></td><td><p>Views and Controllers are event-driven.</p></td></tr><tr><td><p>3.</p></td><td><p>Views are a struct that conforms to the <code>View</code> protocol.</p></td><td><p>Views are a class that conforms to the <code>UIView</code> class.</p></td></tr><tr><td><p>4.</p></td><td><p>Views are passed by value.</p></td><td><p>Views are passed by reference.</p></td></tr><tr><td><p>5.</p></td><td><p>Views stacked one on top of another are allocated on a stack.</p></td><td><p>Views are allocated their own designated space in memory.</p></td></tr><tr><td><p>6.</p></td><td><p>Views do not inherit any stored properties.</p></td><td><p>Views inherit stored properties from the parent class.</p></td></tr><tr><td><p>7.</p></td><td><p>SwiftUI makes use of multiple single-purpose views.</p></td><td><p>UIKit views are multipurpose and can be moulded as per our requirements.</p></td></tr><tr><td><p>8.</p></td><td><p>Views have only one entry point, the <code>body</code>. View maintains a state (source of truth), computes derived values based on the state and updates the body.</p></td><td><p>Views can have multiple entry points for updating their UI. If the happy path of the flow and possible edge cases (unhappy path) are not accounted for, it can lead to UI bugs.</p></td></tr><tr><td><p>9.</p></td><td><p>Views are independent and isolated until they are bonded to other views by a single source of truth.</p></td><td><p>View properties can be altered from anywhere within the code, making it difficult to maintain the state and leaving the code prone to UI bugs.</p></td></tr><tr><td><p>10.</p></td><td><p>Views are lightweight as they only store properties that are declared in them. No additional allocation or reference counting is required.</p></td><td><p>Views can become complex as they store not only the properties that we declare but also the properties of their parent. So, there is an additional allocation that we might not require. Reference counting is required as properties might hold a reference to other classes.</p></td></tr></tbody></table>]]></content:encoded></item><item><title><![CDATA[Understanding SwiftUI - What is SwiftUI? 🧐]]></title><description><![CDATA[Requirements

The minimum deployment target version is iOS 13 and above.

SwiftUI development is supported by Xcode version 11 and above.


The basics

SwiftUI is a UI Design Framework just like its predecessor, the UIKit.

It uses the Declarative pr...]]></description><link>https://blog.shivammaggu.com/understanding-swiftui-what-is-swiftui</link><guid isPermaLink="true">https://blog.shivammaggu.com/understanding-swiftui-what-is-swiftui</guid><category><![CDATA[SwiftUI]]></category><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[Declarative Programming]]></category><dc:creator><![CDATA[Shivam Maggu]]></dc:creator><pubDate>Mon, 20 Feb 2023 21:27:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677191152226/0ae574fd-7e11-4fff-ba4e-b08fdd6e4524.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-requirements">Requirements</h1>
<ul>
<li><p>The <strong>minimum deployment target version is iOS 13</strong> and above.</p>
</li>
<li><p>SwiftUI development is supported by <strong>Xcode version 11</strong> and above.</p>
</li>
</ul>
<h1 id="heading-the-basics">The basics</h1>
<ul>
<li><p>SwiftUI is a <strong>UI Design Framework</strong> just like its predecessor, the UIKit.</p>
</li>
<li><p>It uses the <strong>Declarative</strong> programming approach as opposed to Imperative programming.</p>
</li>
<li><p>In simple words, we inform the framework of <strong>what</strong> should be done based on a state change and it handles the rest.</p>
</li>
<li><p>Whereas in the Imperative approach, we keep track of the state and instruct the framework on <strong>how</strong> to change the UI.</p>
</li>
<li><p>SwiftUI uses <strong>struct</strong> to create views <strong>instead of class</strong>.</p>
</li>
</ul>
<h1 id="heading-pros">Pros</h1>
<ul>
<li><p>SwiftUI helps developers write <strong>simple, clean and less code</strong>.</p>
</li>
<li><p>It <strong>manages the state better</strong> than the Imperative approach of UIKit.</p>
</li>
<li><p>Supports <strong>cross-platform development for iOS/iPadOS, macOS, tvOS and watchOS</strong>.</p>
</li>
<li><p>It uses <strong>Live Previews</strong> instead of building the project to view UI changes.</p>
</li>
<li><p>There are <strong>no auto layout issues</strong> as it always generates satisfiable layouts.</p>
</li>
<li><p>People working in teams <strong>no</strong> longer have to suffer <strong>merge conflicts in storyboards</strong>. 😜</p>
</li>
<li><p>It can be used <strong>alongside UIKit using UIHostingController</strong>.</p>
</li>
<li><p>It supports <strong>reactive programming using ObjectBinding, BindableObject and the Combine Framework</strong>.</p>
</li>
</ul>
<h1 id="heading-cons">Cons</h1>
<ul>
<li><p>Users with iOS version 12 and below will not be able to use apps developed with SwiftUI.</p>
</li>
<li><p>Team development might take more time as others might not be familiar with SwiftUI.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Processing payments using Stripe SDK iOS]]></title><description><![CDATA[Aim
Open the inbuilt Address and Payment Sheet of Stripe and start accepting payments from your customer.
Minimum iOS Deployment Version
12.0
Stripe Frameworks

Stripe (22.8.4)

StripeApplePay (22.8.4)

StripeCore (22.8.4)

StripeUICore (22.8.4)


Re...]]></description><link>https://blog.shivammaggu.com/processing-payments-using-stripe-sdk-ios</link><guid isPermaLink="true">https://blog.shivammaggu.com/processing-payments-using-stripe-sdk-ios</guid><category><![CDATA[stripe]]></category><category><![CDATA[payment gateway]]></category><category><![CDATA[Swift]]></category><category><![CDATA[iOS]]></category><category><![CDATA[stripe ios]]></category><dc:creator><![CDATA[Shivam Maggu]]></dc:creator><pubDate>Wed, 08 Feb 2023 22:10:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1675894757295/7277c76d-2b78-4bec-9b27-869179253684.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-aim">Aim</h1>
<p>Open the inbuilt Address and Payment Sheet of Stripe and start accepting payments from your customer.</p>
<h1 id="heading-minimum-ios-deployment-version">Minimum iOS Deployment Version</h1>
<p>12.0</p>
<h1 id="heading-stripe-frameworks">Stripe Frameworks</h1>
<ol>
<li><p>Stripe (22.8.4)</p>
</li>
<li><p>StripeApplePay (22.8.4)</p>
</li>
<li><p>StripeCore (22.8.4)</p>
</li>
<li><p>StripeUICore (22.8.4)</p>
</li>
</ol>
<h1 id="heading-requirements">Requirements</h1>
<ol>
<li><p>MacBook / MacOS</p>
</li>
<li><p>CocoaPods installed on your system</p>
</li>
<li><p>Xcode</p>
</li>
<li><p>Terminal</p>
</li>
</ol>
<h1 id="heading-proof-of-concept">Proof of Concept</h1>
<p>iOS project and test backend for processing payments with Stripe SDK.</p>
<p><a target="_blank" href="https://github.com/shivammaggu/payments-using-stripe"><img src="https://github.com/fluidicon.png" alt="GitHub - shivammaggu/payments-using-stripe" class="image--center mx-auto" /></a></p>
<h1 id="heading-setup">Setup</h1>
<h2 id="heading-adding-the-stripe-dependency">Adding the Stripe dependency</h2>
<ol>
<li><p>Open <strong>Terminal</strong> in the project directory and do <code>pod init</code>.</p>
</li>
<li><p>Open the newly created <code>Podfile</code> and set the iOS version to <em>12.0.</em></p>
<pre><code class="lang-plaintext"> # Uncomment the next line to define a global platform for your project
 platform :ios, '12.0'
</code></pre>
</li>
<li><p>Add the <strong>Stripe</strong> dependency to the <code>Podfile</code>.</p>
<pre><code class="lang-plaintext"> target 'payments with stripe' do
   # Comment the next line if you don't want to use dynamic frameworks
   use_frameworks!

   # Pods for payments with stripe

   pod 'Stripe'

 end
</code></pre>
</li>
<li><p>Save the <code>Podfile</code> and do a <code>pod install</code> on the <strong>Terminal</strong>. The following dependencies showed be installed.</p>
<blockquote>
<p>pod install</p>
<p>Analyzing dependencies<br />Downloading dependencies<br />Installing Stripe (22.8.4)<br />Installing StripeApplePay (22.8.4)<br />Installing StripeCore (22.8.4)<br />Installing StripeUICore (22.8.4)<br />Generating Pods project<br />Integrating client project</p>
</blockquote>
</li>
<li><p>Close the <code>payments with stripe.xcodeproj</code> and open <code>payments with stripe.xcworkspace</code>.</p>
</li>
<li><p>Select a simulator and run the project. The project should run successfully with a blank screen.</p>
</li>
</ol>
<h2 id="heading-getting-started-with-stripe-developers-dashboard">Getting started with Stripe Developers Dashboard</h2>
<blockquote>
<p>This process should be done once. All the teams will use the same Dashboard. (Backend, iOS, Android etc.)</p>
</blockquote>
<ol>
<li><p>Go to <a target="_blank" href="http://stripe.com">stripe.com</a> and click on <strong>start now</strong>.</p>
</li>
<li><p>Follow the steps as directed by the website to complete the registration process.</p>
</li>
<li><p>Complete the email verification and set up your business details for which payments will be accepted by Stripe.</p>
</li>
<li><p>On the <a target="_blank" href="https://dashboard.stripe.com/apikeys">Stripe Dashboard</a>, you should be able to see your Publishable key and Secret key for both test mode and live mode.</p>
<ol>
<li><p>The <em>publishable key</em> is used on the client side such as in iOS, android or web apps.</p>
</li>
<li><p>The <em>secret key</em> is used on the server side. <strong>(Secret key should not be stored on client-facing apps.)</strong></p>
</li>
</ol>
</li>
</ol>
<h2 id="heading-setup-stripe-in-your-ios-app">Setup Stripe in your iOS App</h2>
<ul>
<li><p>Open <code>AppDelegate.swift</code> and add the <strong>Publishable key</strong> inside the <code>didFinishLaunchingWithOptions</code> method. The publishable key can be retrieved from the Stripe Developers Dashboard.</p>
<pre><code class="lang-swift">  <span class="hljs-type">StripeAPI</span>.defaultPublishableKey = <span class="hljs-string">"Insert your key here"</span>
</code></pre>
<blockquote>
<p>Use the <a target="_blank" href="https://stripe.com/docs/keys#obtain-api-keys">test mode</a> keys during the testing and development phase, and the <a target="_blank" href="https://stripe.com/docs/keys#test-live-modes">live mode</a> keys when publishing the app.</p>
</blockquote>
</li>
<li><p>We need to hit a POST API to create the payment intent and in return receive the following data. This data is used to initialize the <code>customer</code>, Stripe SDK and the <code>PaymentSheet</code>.</p>
<ol>
<li><p><code>customerId</code></p>
</li>
<li><p><code>ephemeralKey</code></p>
</li>
<li><p><code>paymentIntent</code></p>
</li>
<li><p><code>publishableKey</code></p>
</li>
</ol>
</li>
</ul>
<p>Refer to <code>CourseCheckoutViewModel</code> in the Proof of Concept example on GitHub.</p>
<pre><code class="lang-swift"><span class="hljs-comment">// Creates a payment intent for the customer and fetches the important keys and id's for processing a payment</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">fetchPaymentIntent</span><span class="hljs-params">(completion: @escaping <span class="hljs-params">(<span class="hljs-params">(Bool, String?)</span></span></span></span> -&gt; <span class="hljs-type">Void</span>)) {

    <span class="hljs-comment">// This could be the details for the item that the customer wants to buy</span>

    <span class="hljs-keyword">let</span> cartContent: [<span class="hljs-type">String</span>: <span class="hljs-type">Any</span>] = [<span class="hljs-string">"items"</span>: [[<span class="hljs-string">"id"</span>: <span class="hljs-type">UUID</span>().uuidString]]]

    <span class="hljs-keyword">self</span>.apiClient.createPaymentIntent(cartContent: cartContent) { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] (data, error) <span class="hljs-keyword">in</span>

        <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> <span class="hljs-keyword">self</span> = <span class="hljs-keyword">self</span> <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }

        <span class="hljs-keyword">guard</span> error == <span class="hljs-literal">nil</span> <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">print</span>(error.debugDescription)
            completion(<span class="hljs-literal">false</span>, error.debugDescription)
            <span class="hljs-keyword">return</span>
        }

        <span class="hljs-keyword">guard</span>
            <span class="hljs-keyword">let</span> customerId = data?.customerId <span class="hljs-keyword">as</span>? <span class="hljs-type">String</span>,
            <span class="hljs-keyword">let</span> customerEphemeralKeySecret = data?.ephemeralKey <span class="hljs-keyword">as</span>? <span class="hljs-type">String</span>,
            <span class="hljs-keyword">let</span> paymentIntentClientSecret = data?.paymentIntent <span class="hljs-keyword">as</span>? <span class="hljs-type">String</span>,
            <span class="hljs-keyword">let</span> publishableKey = data?.publishableKey <span class="hljs-keyword">as</span>? <span class="hljs-type">String</span>
        <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">let</span> error = <span class="hljs-string">"Error fetching required data"</span>
            <span class="hljs-built_in">print</span>(error)
            completion(<span class="hljs-literal">false</span>, error)
            <span class="hljs-keyword">return</span>
        }

        <span class="hljs-built_in">print</span>(<span class="hljs-string">"Created Payment Intent"</span>)

        <span class="hljs-keyword">self</span>.setPublishableKey(publishableKey: publishableKey)
        <span class="hljs-keyword">self</span>.paymentIntentClientSecret = paymentIntentClientSecret
        <span class="hljs-keyword">self</span>.customer = .<span class="hljs-keyword">init</span>(id: customerId,
                              ephemeralKeySecret: customerEphemeralKeySecret)
        completion(<span class="hljs-literal">true</span>, <span class="hljs-literal">nil</span>)
    }
}
</code></pre>
<ul>
<li>Setup the <code>PaymentSheet</code> in your View Controller. Refer to <code>CourseCheckoutViewController</code> in the Proof of Concept example on GitHub.</li>
</ul>
<pre><code class="lang-swift"><span class="hljs-comment">// Create and configure the payment sheet</span>

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">createPaymentSheet</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">var</span> configuration = <span class="hljs-type">PaymentSheet</span>.<span class="hljs-type">Configuration</span>()

    <span class="hljs-comment">// Autofills the shipping details with the one we collected from user through AddressViewController</span>

    configuration.shippingDetails = { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] <span class="hljs-keyword">in</span>
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>?.address
    }

    <span class="hljs-comment">// Configure button for Apple Pay in PaymentSheet</span>

    configuration.applePay = .<span class="hljs-keyword">init</span>(merchantId: <span class="hljs-string">"com.example.appname"</span>,
                                    merchantCountryCode: <span class="hljs-string">"US"</span>)

    <span class="hljs-comment">// Provides checkbox for saving card details</span>

    configuration.savePaymentMethodOptInBehavior = .requiresOptIn

    <span class="hljs-comment">// Sets the return url. Used when user needs to go outside the app for authentication</span>

    configuration.returnURL = <span class="hljs-string">"payments-with-stripe://stripe-redirect"</span>

    <span class="hljs-comment">// Configures the color of the pay button</span>

    configuration.primaryButtonColor = .blue

    <span class="hljs-comment">// Shows the name of the company collecting the payment</span>

    configuration.merchantDisplayName = <span class="hljs-string">"Dummy Corp Inc"</span>

    <span class="hljs-comment">// Allows payments which requires some time to confirm</span>

    configuration.allowsDelayedPaymentMethods = <span class="hljs-literal">true</span>


    <span class="hljs-comment">// Adds default billing details for user to the payment object</span>

    configuration.defaultBillingDetails.email = <span class="hljs-string">"dummy@corp.com"</span>
    configuration.defaultBillingDetails.name = <span class="hljs-keyword">self</span>.address?.name
    configuration.defaultBillingDetails.phone = <span class="hljs-keyword">self</span>.address?.phone
    configuration.defaultBillingDetails.address.country = <span class="hljs-keyword">self</span>.address?.address.country
    configuration.defaultBillingDetails.address.city = <span class="hljs-keyword">self</span>.address?.address.city
    configuration.defaultBillingDetails.address.line1 = <span class="hljs-keyword">self</span>.address?.address.line1
    configuration.defaultBillingDetails.address.line2 = <span class="hljs-keyword">self</span>.address?.address.line2
    configuration.defaultBillingDetails.address.state = <span class="hljs-keyword">self</span>.address?.address.state
    configuration.defaultBillingDetails.address.postalCode = <span class="hljs-keyword">self</span>.address?.address.postalCode

    <span class="hljs-comment">// Sets customer object to payment object</span>

    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> customer = <span class="hljs-keyword">self</span>.viewModel.getCustomer() {
        configuration.customer = customer
    }

    <span class="hljs-comment">// Initiaises the PaymentSheet with the above config and client secret received from payment intent API</span>

    <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> paymentIntentClientSecret = <span class="hljs-keyword">self</span>.viewModel.getPaymentIntentClientSecret() {
        <span class="hljs-keyword">self</span>.paymentSheet = <span class="hljs-type">PaymentSheet</span>(paymentIntentClientSecret: paymentIntentClientSecret,
                                        configuration: configuration)
    }
}
</code></pre>
<ul>
<li>Handle the conditions after the payment. The following three scenarios can happen during/after the payment.</li>
</ul>
<pre><code class="lang-swift"><span class="hljs-comment">// Present user with the payment sheet and handles the various payment scenarios</span>

<span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentPaymentSheet</span><span class="hljs-params">()</span></span> {
    <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> paymentSheet = paymentSheet <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }

    <span class="hljs-type">DispatchQueue</span>.main.async {
        paymentSheet.present(from: <span class="hljs-keyword">self</span>) { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] (paymentResult) <span class="hljs-keyword">in</span>

            <span class="hljs-keyword">guard</span> <span class="hljs-keyword">let</span> <span class="hljs-keyword">self</span> = <span class="hljs-keyword">self</span> <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> }

            <span class="hljs-keyword">switch</span> paymentResult {
            <span class="hljs-keyword">case</span> .completed:
                <span class="hljs-keyword">self</span>.displayAlert(title: <span class="hljs-string">"Payment complete!"</span>)
            <span class="hljs-keyword">case</span> .canceled:
                <span class="hljs-keyword">self</span>.displayAlert(title: <span class="hljs-string">"Payment canceled!"</span>)
            <span class="hljs-keyword">case</span> .failed(<span class="hljs-keyword">let</span> error):
                <span class="hljs-keyword">self</span>.displayAlert(title: <span class="hljs-string">"Payment failed"</span>, message: error.localizedDescription)
            }
        }
    }
}
</code></pre>
<ul>
<li><p>On tap of the pay now button, present the payment sheet, add the payment details and click on pay.</p>
</li>
<li><p>Either the payment will be completed or it will fail if there are issues with the payment method. We can debug the failure reasons in the following <a target="_blank" href="https://stripe.com/docs/error-codes">link</a>.</p>
</li>
<li><p>Payment can also result in cancellation if the user dismisses the <code>PaymentSheet</code>.</p>
</li>
</ul>
<h2 id="heading-address-collection">Address collection</h2>
<ol>
<li><p>We can set up the <code>AddressViewController</code> to display a form for collecting names, addresses, cities, countries, postal codes and phone numbers.</p>
<pre><code class="lang-swift"> <span class="hljs-comment">// Setup and display the address sheet</span>

 <span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">presentAddressCollectionController</span><span class="hljs-params">(<span class="hljs-number">_</span> controller: CourseCheckoutViewController)</span></span> {
     <span class="hljs-keyword">let</span> addressConfiguration = <span class="hljs-type">AddressViewController</span>.<span class="hljs-type">Configuration</span>(additionalFields: .<span class="hljs-keyword">init</span>(name: .<span class="hljs-keyword">required</span>,
                                                                                            phone: .<span class="hljs-keyword">required</span>),
                                                                     allowedCountries: [],
                                                                     title: <span class="hljs-string">"Billing Address"</span>)
     <span class="hljs-keyword">let</span> addressViewController = <span class="hljs-type">AddressViewController</span>(configuration: addressConfiguration,
                                                       delegate: controller)

     <span class="hljs-keyword">let</span> navigationController = <span class="hljs-type">UINavigationController</span>(rootViewController: addressViewController)
     <span class="hljs-keyword">self</span>.rootViewController.present(navigationController, animated: <span class="hljs-literal">true</span>)
 }
</code></pre>
</li>
<li><p>Set up the delegate method to get the address details filled in by the customer.</p>
<pre><code class="lang-swift"> <span class="hljs-class"><span class="hljs-keyword">extension</span> <span class="hljs-title">CourseCheckoutViewController</span>: <span class="hljs-title">AddressViewControllerDelegate</span> </span>{

     <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">addressViewControllerDidFinish</span><span class="hljs-params">(<span class="hljs-number">_</span> addressViewController: Stripe.AddressViewController, with address: Stripe.AddressViewController.AddressDetails?)</span></span> {
         addressViewController.dismiss(animated: <span class="hljs-literal">true</span>) {
             <span class="hljs-built_in">debugPrint</span>(address <span class="hljs-keyword">as</span> <span class="hljs-type">Any</span>)

             <span class="hljs-keyword">self</span>.address = address
         }
     }
 }
</code></pre>
</li>
<li><p>The address details collected from the user should be passed to <code>PaymentSheet</code> <code>Configuration</code>. This will auto-fill the details in the <code>PaymentSheet</code> and also send the customer details to Stripe Dashboard.</p>
</li>
<li><p>Collecting these details is essential if the Stripe account is registered in India but the users are paying in international currency. More on this <a target="_blank" href="https://stripe.com/docs/india-accept-international-payments">here</a>.</p>
<pre><code class="lang-swift"> <span class="hljs-keyword">var</span> configuration = <span class="hljs-type">PaymentSheet</span>.<span class="hljs-type">Configuration</span>()

 configuration.shippingDetails = { [<span class="hljs-keyword">weak</span> <span class="hljs-keyword">self</span>] <span class="hljs-keyword">in</span>
     <span class="hljs-keyword">return</span> <span class="hljs-keyword">self</span>?.address
 }
</code></pre>
</li>
</ol>
<h2 id="heading-enable-card-scanning">Enable card scanning</h2>
<p>To enable the option for scanning debit/credit cards, add the following key to the <code>info.plist</code> file.</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">key</span>&gt;</span>NSCameraUsageDescription<span class="hljs-tag">&lt;/<span class="hljs-name">key</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">string</span>&gt;</span>Allow the app to scan cards.<span class="hljs-tag">&lt;/<span class="hljs-name">string</span>&gt;</span>
</code></pre>
<blockquote>
<p>This feature is available on iOS 13.0 and above.</p>
</blockquote>
<h2 id="heading-process-payments-using-apple-pay">Process payments using Apple Pay</h2>
<p>Steps for integrating Apple Pay are well documented in the links below.</p>
<p>Please check out this tutorial for adding Apple Pay as a payment option to your app.</p>
<ol>
<li><p><a target="_blank" href="https://stripe.com/docs/payments/accept-a-payment?platform=ios&amp;ui=payment-sheet#ios-apple-pay">Apple Pay with Payment Sheet</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/apple-pay">Setup Apple Pay button</a></p>
</li>
</ol>
<p>Add an Apple pay button on your <code>PaymentSheet</code> by editing the configuration as follows.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">var</span> configuration = <span class="hljs-type">PaymentSheet</span>.<span class="hljs-type">Configuration</span>()
configuration.applePay = .<span class="hljs-keyword">init</span>(merchantId: <span class="hljs-string">"merchant.com.your_app_name"</span>,
                               merchantCountryCode: <span class="hljs-string">"US"</span>)
</code></pre>
<h2 id="heading-return-to-the-app-automatically-after-external-authentication">Return to the app automatically after external authentication</h2>
<p>A customer may go out of your app for authentication purposes, for example, in Safari or their banking app to complete payment.</p>
<p>To allow them to automatically return to your app after authenticating, <a target="_blank" href="https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app">configure a custom URL scheme</a> or <a target="_blank" href="https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content">universal link</a> and set up your app delegate/scene delegate</p>
<p>(If using scene delegate for iOS 13 and above) to forward the URL to the SDK.</p>
<ul>
<li><p><strong>AppDelegate.swift</strong></p>
<pre><code class="lang-swift">  <span class="hljs-comment">// This method handles opening custom URL schemes</span>
  <span class="hljs-comment">// (for example, "your-app://stripe-redirect")</span>

  <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">application</span><span class="hljs-params">(<span class="hljs-number">_</span> app: UIApplication,
                    <span class="hljs-keyword">open</span> url: URL, 
                    options: [UIApplication.OpenURLOptionsKey : <span class="hljs-keyword">Any</span>] = [:]
  )</span></span> -&gt; <span class="hljs-type">Bool</span> {

      <span class="hljs-keyword">let</span> stripeHandled = <span class="hljs-type">StripeAPI</span>.handleURLCallback(with: url)

      <span class="hljs-keyword">if</span> !stripeHandled {

          <span class="hljs-comment">/*
            This was not a stripe url, do whatever url handling your app
            normally does, if any.
            */</span>
      }

      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
  }

  <span class="hljs-comment">// This method handles opening universal link URLs</span>
  <span class="hljs-comment">// (for example, "https://example.com/stripe_ios_callback")</span>

  <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">application</span><span class="hljs-params">(<span class="hljs-number">_</span> application: UIApplication,
                    <span class="hljs-keyword">continue</span> userActivity: NSUserActivity,
                    restorationHandler: @escaping <span class="hljs-params">([UIUserActivityRestoring]?)</span></span></span> -&gt; <span class="hljs-type">Void</span>
  ) -&gt; <span class="hljs-type">Bool</span>  {

      <span class="hljs-keyword">if</span> userActivity.activityType == <span class="hljs-type">NSUserActivityTypeBrowsingWeb</span> {

          <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> url = userActivity.webpageURL {

              <span class="hljs-keyword">let</span> stripeHandled = <span class="hljs-type">StripeAPI</span>.handleURLCallback(with: url)

              <span class="hljs-keyword">guard</span> !stripeHandled <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span> }

              <span class="hljs-comment">/*
                This was not a stripe url, do whatever url handling your app
                normally does, if any.
                */</span>
          }
      }

      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
  }
</code></pre>
</li>
<li><p><strong>SceneDelegate.swift</strong></p>
<pre><code class="lang-swift">  <span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">scene</span><span class="hljs-params">(<span class="hljs-number">_</span> scene: UIScene,
              openURLContexts URLContexts: Set&lt;UIOpenURLContext&gt;)</span></span> {

      <span class="hljs-keyword">if</span> <span class="hljs-keyword">let</span> urlContext = <span class="hljs-type">URLContexts</span>.first {

          <span class="hljs-keyword">let</span> url = urlContext.url
          <span class="hljs-keyword">let</span> stripeHandled = <span class="hljs-type">StripeAPI</span>.handleURLCallback(with: url)

          <span class="hljs-keyword">if</span> !stripeHandled {
              <span class="hljs-comment">// This was not a stripe url, do whatever url handling your app</span>
              <span class="hljs-comment">// normally does, if any.</span>
          }
      }
  }
</code></pre>
</li>
</ul>
<p>Also, a <code>returnURL</code> should be configured in the <code>PaymentSheet.Configuration</code> object to the URL for your app.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">var</span> configuration = <span class="hljs-type">PaymentSheet</span>.<span class="hljs-type">Configuration</span>()
configuration.returnURL = <span class="hljs-string">"payments-with-stripe://stripe-redirect"</span>
</code></pre>
<h2 id="heading-clear-cookies-on-logout">Clear cookies on logout</h2>
<p><code>PaymentSheet</code> related cookies and other authentication data should be cleared when a user logs out of the app. Add the following code to your logout function.</p>
<pre><code class="lang-swift"><span class="hljs-keyword">import</span> Stripe

<span class="hljs-comment">// Resets all payment and user related cache</span>
<span class="hljs-comment">// Should be called when user logs out of the app</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">logout</span><span class="hljs-params">()</span></span> {

    <span class="hljs-comment">/*
      Perform all other logout related steps here
      */</span>

    <span class="hljs-type">PaymentSheet</span>.resetCustomer()
}
</code></pre>
<h1 id="heading-faq">FAQ</h1>
<ol>
<li><p>Stripe SDK can be installed using CocoaPods, Carthage, and Swift Package Manager.</p>
</li>
<li><p>The latest version <a target="_blank" href="https://github.com/stripe/stripe-ios/tree/23.3.2">v23.3.2</a> or above of Stripe iOS SDK requires Xcode 13.2.1 or later and is compatible with apps targeting iOS 13 or above.</p>
</li>
<li><p>For iOS 12 support use → <a target="_blank" href="https://github.com/stripe/stripe-ios/tree/22.8.4">v22.8.4</a>.</p>
</li>
<li><p>For iOS 11 support use → <a target="_blank" href="https://github.com/stripe/stripe-ios/tree/21.13.0">v21.13.0</a>.</p>
</li>
<li><p>For iOS 10 support use → <a target="_blank" href="https://github.com/stripe/stripe-ios/tree/v19.4.0">v19.4.0</a>.</p>
</li>
<li><p>For iOS 9 support use → <a target="_blank" href="https://github.com/stripe/stripe-ios/tree/v17.0.2">v17.0.2</a>.</p>
</li>
<li><p>Why was my card declined when trying to make a payment? Check the reason for your <a target="_blank" href="https://stripe.com/docs/declines/codes">decline code</a>.</p>
</li>
<li><p>Got an error message while processing your payment? Check the reason in this <a target="_blank" href="https://stripe.com/docs/error-codes">article</a>.</p>
</li>
</ol>
<h1 id="heading-resources">Resources</h1>
<ol>
<li><p><a target="_blank" href="https://cocoapods.org/pods/Stripe">Stripe SDK on CocoaPods</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/stripe/stripe-ios">Stripe SDK on GitHub</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/api">Stripe API Reference</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs">Stripe Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.dev/stripe-ios/documentation/stripe/">Stripe iOS SDK Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/libraries/ios">Installing Stripe SDK using CocoaPods</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/keys#obtain-api-keys">About API Keys</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/keys#test-live-modes">Test mode and Live mode</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/payments/accept-a-payment">Accepting Payments with Stripe</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/elements/address-element/collect-addresses">Collecting User's Address</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/testing">Dummy Credentials for testing payments</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/india-accept-international-payments">Accept payment from regions outside of India</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/payments/payment-methods/integration-options#payment-method-product-support">Adding multiple payment methods</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/declines/codes">Debug card Decline Codes</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/error-codes">Understanding payment Error Codes</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/payments/checkout/phone-numbers">Collecting user phone numbers through the backend</a></p>
</li>
<li><p>Adding additional articles on security when processing payments using Stripe:</p>
<ol>
<li><p><a target="_blank" href="https://stripe.com/docs/strong-customer-authentication">Strong Customer Authentication</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/security">Security</a></p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/payments/3d-secure">3-D Secure</a></p>
</li>
<li><p><a target="_blank" href="https://support.stripe.com/questions/stripe-ios-sdk-privacy-details">Privacy Details for iOS</a></p>
</li>
</ol>
</li>
</ol>
]]></content:encoded></item></channel></rss>