├── Podfile ├── Podfile.lock ├── README.md ├── Stripe-SwiftUI.xcodeproj └── project.pbxproj ├── Stripe-SwiftUI.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── swiftpm │ └── Package.resolved ├── Stripe-SwiftUI ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Color.colorset │ │ └── Contents.json │ ├── Contents.json │ └── cake.imageset │ │ ├── Contents.json │ │ └── cake.jpg ├── Base.lproj │ └── LaunchScreen.storyboard ├── Extensions.swift ├── Info.plist ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SceneDelegate.swift ├── StripeApi │ ├── MyApiClient.swift │ └── PaymentContextDelegate.swift ├── View Models │ └── AboutYouViewModel.swift └── Views │ ├── ContentView.swift │ ├── InitialView.swift │ └── ShoppingView.swift └── image ├── ss1.png ├── ss2.png ├── ss3.png ├── ss4.png ├── ss5.png ├── ss6.png └── ss7.png /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '13.0' 3 | 4 | target 'Stripe-SwiftUI' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for Stripe-SwiftUI 9 | pod 'Stripe' 10 | 11 | end 12 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Stripe (19.0.1) 3 | 4 | DEPENDENCIES: 5 | - Stripe 6 | 7 | SPEC REPOS: 8 | trunk: 9 | - Stripe 10 | 11 | SPEC CHECKSUMS: 12 | Stripe: 03313f9520a0786e2c00d9a7a2672c11e08821af 13 | 14 | PODFILE CHECKSUM: c7b0ad1360577b38e74f2c5fe3116ad6c4e7d04a 15 | 16 | COCOAPODS: 1.9.1 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stripe-swiftui 2 | This is an example iOS project using Stripe with SwiftUI. You will need to have your own backend ready and add your stripe key to the app delegate and the custom backend url in MYApiClient file. There are warnings in the project made which will remind you. 3 | 4 | Please give my project a star. 5 | I take donations via paypal if this code was helpful to you in any way. My paypal email: ngftlaudhosp@yahoo.com 6 | 7 | ![Create user view](https://github.com/nelglez/stripe-swiftui/blob/master/image/ss1.png) 8 | 9 | ![Checkout view](https://github.com/nelglez/stripe-swiftui/blob/master/image/ss2.png) 10 | 11 | ![Add payment method view](https://github.com/nelglez/stripe-swiftui/blob/master/image/ss3.png) 12 | 13 | ![Added a payment method now you can pay](https://github.com/nelglez/stripe-swiftui/blob/master/image/ss4.png) 14 | 15 | ![Shipping View](https://github.com/nelglez/stripe-swiftui/blob/master/image/ss5.png) 16 | 17 | ![Select shipping options](https://github.com/nelglez/stripe-swiftui/blob/master/image/ss6.png) 18 | 19 | ![Successfully paid alert](https://github.com/nelglez/stripe-swiftui/blob/master/image/ss7.png) 20 | -------------------------------------------------------------------------------- /Stripe-SwiftUI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 40A039B20CD01F2B08A2235D /* Pods_Stripe_SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62F1339BAFE4B44F7A45D8CF /* Pods_Stripe_SwiftUI.framework */; }; 11 | 7A720DF624452C3100F72E8C /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A720DF524452C3100F72E8C /* Extensions.swift */; }; 12 | 7A720DF82445DE6E00F72E8C /* PaymentContextDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A720DF72445DE6E00F72E8C /* PaymentContextDelegate.swift */; }; 13 | 7A720DFB2445E47B00F72E8C /* IQKeyboardManagerSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A720DFA2445E47B00F72E8C /* IQKeyboardManagerSwift */; }; 14 | 7AE8EA0E24450754007B099F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8EA0D24450754007B099F /* AppDelegate.swift */; }; 15 | 7AE8EA1024450754007B099F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8EA0F24450754007B099F /* SceneDelegate.swift */; }; 16 | 7AE8EA1224450754007B099F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8EA1124450754007B099F /* ContentView.swift */; }; 17 | 7AE8EA1424450758007B099F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7AE8EA1324450758007B099F /* Assets.xcassets */; }; 18 | 7AE8EA1724450758007B099F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7AE8EA1624450758007B099F /* Preview Assets.xcassets */; }; 19 | 7AE8EA1A24450758007B099F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7AE8EA1824450758007B099F /* LaunchScreen.storyboard */; }; 20 | 7AE8EA22244508BD007B099F /* AboutYouViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8EA21244508BD007B099F /* AboutYouViewModel.swift */; }; 21 | 7AE8EA242445158B007B099F /* MyApiClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8EA232445158B007B099F /* MyApiClient.swift */; }; 22 | 7AE8EA26244517EF007B099F /* InitialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8EA25244517EF007B099F /* InitialView.swift */; }; 23 | 7AE8EA2824451868007B099F /* ShoppingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE8EA2724451868007B099F /* ShoppingView.swift */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 39FE6FB41AB17FE3E297CFDD /* Pods-Stripe-SwiftUI.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stripe-SwiftUI.debug.xcconfig"; path = "Target Support Files/Pods-Stripe-SwiftUI/Pods-Stripe-SwiftUI.debug.xcconfig"; sourceTree = ""; }; 28 | 54E7F0501F36F760B85D39A7 /* Pods-Stripe-SwiftUI.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Stripe-SwiftUI.release.xcconfig"; path = "Target Support Files/Pods-Stripe-SwiftUI/Pods-Stripe-SwiftUI.release.xcconfig"; sourceTree = ""; }; 29 | 62F1339BAFE4B44F7A45D8CF /* Pods_Stripe_SwiftUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Stripe_SwiftUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 7A720DF524452C3100F72E8C /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 31 | 7A720DF72445DE6E00F72E8C /* PaymentContextDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentContextDelegate.swift; sourceTree = ""; }; 32 | 7AE8EA0A24450754007B099F /* Stripe-SwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Stripe-SwiftUI.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 7AE8EA0D24450754007B099F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 34 | 7AE8EA0F24450754007B099F /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 35 | 7AE8EA1124450754007B099F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 36 | 7AE8EA1324450758007B099F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | 7AE8EA1624450758007B099F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 38 | 7AE8EA1924450758007B099F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 39 | 7AE8EA1B24450758007B099F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 7AE8EA21244508BD007B099F /* AboutYouViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutYouViewModel.swift; sourceTree = ""; }; 41 | 7AE8EA232445158B007B099F /* MyApiClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyApiClient.swift; sourceTree = ""; }; 42 | 7AE8EA25244517EF007B099F /* InitialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialView.swift; sourceTree = ""; }; 43 | 7AE8EA2724451868007B099F /* ShoppingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShoppingView.swift; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 7AE8EA0724450754007B099F /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | 40A039B20CD01F2B08A2235D /* Pods_Stripe_SwiftUI.framework in Frameworks */, 52 | 7A720DFB2445E47B00F72E8C /* IQKeyboardManagerSwift in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | 103F8B830D66E685BC29B1F6 /* Frameworks */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 62F1339BAFE4B44F7A45D8CF /* Pods_Stripe_SwiftUI.framework */, 63 | ); 64 | name = Frameworks; 65 | sourceTree = ""; 66 | }; 67 | 7A720DF224451D9B00F72E8C /* StripeApi */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 7AE8EA232445158B007B099F /* MyApiClient.swift */, 71 | 7A720DF72445DE6E00F72E8C /* PaymentContextDelegate.swift */, 72 | ); 73 | path = StripeApi; 74 | sourceTree = ""; 75 | }; 76 | 7A720DF324451DA800F72E8C /* View Models */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 7AE8EA21244508BD007B099F /* AboutYouViewModel.swift */, 80 | ); 81 | path = "View Models"; 82 | sourceTree = ""; 83 | }; 84 | 7A720DF424451DB900F72E8C /* Views */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 7AE8EA25244517EF007B099F /* InitialView.swift */, 88 | 7AE8EA1124450754007B099F /* ContentView.swift */, 89 | 7AE8EA2724451868007B099F /* ShoppingView.swift */, 90 | ); 91 | path = Views; 92 | sourceTree = ""; 93 | }; 94 | 7AE8EA0124450753007B099F = { 95 | isa = PBXGroup; 96 | children = ( 97 | 7AE8EA0C24450754007B099F /* Stripe-SwiftUI */, 98 | 7AE8EA0B24450754007B099F /* Products */, 99 | B323FE5762E07B5FEF0D592C /* Pods */, 100 | 103F8B830D66E685BC29B1F6 /* Frameworks */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | 7AE8EA0B24450754007B099F /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 7AE8EA0A24450754007B099F /* Stripe-SwiftUI.app */, 108 | ); 109 | name = Products; 110 | sourceTree = ""; 111 | }; 112 | 7AE8EA0C24450754007B099F /* Stripe-SwiftUI */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 7AE8EA0D24450754007B099F /* AppDelegate.swift */, 116 | 7AE8EA0F24450754007B099F /* SceneDelegate.swift */, 117 | 7A720DF524452C3100F72E8C /* Extensions.swift */, 118 | 7A720DF224451D9B00F72E8C /* StripeApi */, 119 | 7A720DF324451DA800F72E8C /* View Models */, 120 | 7A720DF424451DB900F72E8C /* Views */, 121 | 7AE8EA1324450758007B099F /* Assets.xcassets */, 122 | 7AE8EA1824450758007B099F /* LaunchScreen.storyboard */, 123 | 7AE8EA1B24450758007B099F /* Info.plist */, 124 | 7AE8EA1524450758007B099F /* Preview Content */, 125 | ); 126 | path = "Stripe-SwiftUI"; 127 | sourceTree = ""; 128 | }; 129 | 7AE8EA1524450758007B099F /* Preview Content */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 7AE8EA1624450758007B099F /* Preview Assets.xcassets */, 133 | ); 134 | path = "Preview Content"; 135 | sourceTree = ""; 136 | }; 137 | B323FE5762E07B5FEF0D592C /* Pods */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 39FE6FB41AB17FE3E297CFDD /* Pods-Stripe-SwiftUI.debug.xcconfig */, 141 | 54E7F0501F36F760B85D39A7 /* Pods-Stripe-SwiftUI.release.xcconfig */, 142 | ); 143 | path = Pods; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | 7AE8EA0924450754007B099F /* Stripe-SwiftUI */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 7AE8EA1E24450758007B099F /* Build configuration list for PBXNativeTarget "Stripe-SwiftUI" */; 152 | buildPhases = ( 153 | 655D69C33CAA1867B230C143 /* [CP] Check Pods Manifest.lock */, 154 | 7AE8EA0624450754007B099F /* Sources */, 155 | 7AE8EA0724450754007B099F /* Frameworks */, 156 | 7AE8EA0824450754007B099F /* Resources */, 157 | 3A1356B214ACDF8053F35AC5 /* [CP] Embed Pods Frameworks */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | ); 163 | name = "Stripe-SwiftUI"; 164 | packageProductDependencies = ( 165 | 7A720DFA2445E47B00F72E8C /* IQKeyboardManagerSwift */, 166 | ); 167 | productName = "Stripe-SwiftUI"; 168 | productReference = 7AE8EA0A24450754007B099F /* Stripe-SwiftUI.app */; 169 | productType = "com.apple.product-type.application"; 170 | }; 171 | /* End PBXNativeTarget section */ 172 | 173 | /* Begin PBXProject section */ 174 | 7AE8EA0224450753007B099F /* Project object */ = { 175 | isa = PBXProject; 176 | attributes = { 177 | LastSwiftUpdateCheck = 1140; 178 | LastUpgradeCheck = 1140; 179 | ORGANIZATIONNAME = "Nelson Gonzalez"; 180 | TargetAttributes = { 181 | 7AE8EA0924450754007B099F = { 182 | CreatedOnToolsVersion = 11.4; 183 | }; 184 | }; 185 | }; 186 | buildConfigurationList = 7AE8EA0524450753007B099F /* Build configuration list for PBXProject "Stripe-SwiftUI" */; 187 | compatibilityVersion = "Xcode 9.3"; 188 | developmentRegion = en; 189 | hasScannedForEncodings = 0; 190 | knownRegions = ( 191 | en, 192 | Base, 193 | ); 194 | mainGroup = 7AE8EA0124450753007B099F; 195 | packageReferences = ( 196 | 7A720DF92445E47B00F72E8C /* XCRemoteSwiftPackageReference "IQKeyboardManager" */, 197 | ); 198 | productRefGroup = 7AE8EA0B24450754007B099F /* Products */; 199 | projectDirPath = ""; 200 | projectRoot = ""; 201 | targets = ( 202 | 7AE8EA0924450754007B099F /* Stripe-SwiftUI */, 203 | ); 204 | }; 205 | /* End PBXProject section */ 206 | 207 | /* Begin PBXResourcesBuildPhase section */ 208 | 7AE8EA0824450754007B099F /* Resources */ = { 209 | isa = PBXResourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | 7AE8EA1A24450758007B099F /* LaunchScreen.storyboard in Resources */, 213 | 7AE8EA1724450758007B099F /* Preview Assets.xcassets in Resources */, 214 | 7AE8EA1424450758007B099F /* Assets.xcassets in Resources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXShellScriptBuildPhase section */ 221 | 3A1356B214ACDF8053F35AC5 /* [CP] Embed Pods Frameworks */ = { 222 | isa = PBXShellScriptBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | ); 226 | inputFileListPaths = ( 227 | "${PODS_ROOT}/Target Support Files/Pods-Stripe-SwiftUI/Pods-Stripe-SwiftUI-frameworks-${CONFIGURATION}-input-files.xcfilelist", 228 | ); 229 | name = "[CP] Embed Pods Frameworks"; 230 | outputFileListPaths = ( 231 | "${PODS_ROOT}/Target Support Files/Pods-Stripe-SwiftUI/Pods-Stripe-SwiftUI-frameworks-${CONFIGURATION}-output-files.xcfilelist", 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | shellPath = /bin/sh; 235 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Stripe-SwiftUI/Pods-Stripe-SwiftUI-frameworks.sh\"\n"; 236 | showEnvVarsInLog = 0; 237 | }; 238 | 655D69C33CAA1867B230C143 /* [CP] Check Pods Manifest.lock */ = { 239 | isa = PBXShellScriptBuildPhase; 240 | buildActionMask = 2147483647; 241 | files = ( 242 | ); 243 | inputFileListPaths = ( 244 | ); 245 | inputPaths = ( 246 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 247 | "${PODS_ROOT}/Manifest.lock", 248 | ); 249 | name = "[CP] Check Pods Manifest.lock"; 250 | outputFileListPaths = ( 251 | ); 252 | outputPaths = ( 253 | "$(DERIVED_FILE_DIR)/Pods-Stripe-SwiftUI-checkManifestLockResult.txt", 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | shellPath = /bin/sh; 257 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 258 | showEnvVarsInLog = 0; 259 | }; 260 | /* End PBXShellScriptBuildPhase section */ 261 | 262 | /* Begin PBXSourcesBuildPhase section */ 263 | 7AE8EA0624450754007B099F /* Sources */ = { 264 | isa = PBXSourcesBuildPhase; 265 | buildActionMask = 2147483647; 266 | files = ( 267 | 7A720DF624452C3100F72E8C /* Extensions.swift in Sources */, 268 | 7AE8EA0E24450754007B099F /* AppDelegate.swift in Sources */, 269 | 7AE8EA242445158B007B099F /* MyApiClient.swift in Sources */, 270 | 7A720DF82445DE6E00F72E8C /* PaymentContextDelegate.swift in Sources */, 271 | 7AE8EA26244517EF007B099F /* InitialView.swift in Sources */, 272 | 7AE8EA1024450754007B099F /* SceneDelegate.swift in Sources */, 273 | 7AE8EA2824451868007B099F /* ShoppingView.swift in Sources */, 274 | 7AE8EA22244508BD007B099F /* AboutYouViewModel.swift in Sources */, 275 | 7AE8EA1224450754007B099F /* ContentView.swift in Sources */, 276 | ); 277 | runOnlyForDeploymentPostprocessing = 0; 278 | }; 279 | /* End PBXSourcesBuildPhase section */ 280 | 281 | /* Begin PBXVariantGroup section */ 282 | 7AE8EA1824450758007B099F /* LaunchScreen.storyboard */ = { 283 | isa = PBXVariantGroup; 284 | children = ( 285 | 7AE8EA1924450758007B099F /* Base */, 286 | ); 287 | name = LaunchScreen.storyboard; 288 | sourceTree = ""; 289 | }; 290 | /* End PBXVariantGroup section */ 291 | 292 | /* Begin XCBuildConfiguration section */ 293 | 7AE8EA1C24450758007B099F /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ALWAYS_SEARCH_USER_PATHS = NO; 297 | CLANG_ANALYZER_NONNULL = YES; 298 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 299 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 300 | CLANG_CXX_LIBRARY = "libc++"; 301 | CLANG_ENABLE_MODULES = YES; 302 | CLANG_ENABLE_OBJC_ARC = YES; 303 | CLANG_ENABLE_OBJC_WEAK = YES; 304 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 305 | CLANG_WARN_BOOL_CONVERSION = YES; 306 | CLANG_WARN_COMMA = YES; 307 | CLANG_WARN_CONSTANT_CONVERSION = YES; 308 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 310 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 311 | CLANG_WARN_EMPTY_BODY = YES; 312 | CLANG_WARN_ENUM_CONVERSION = YES; 313 | CLANG_WARN_INFINITE_RECURSION = YES; 314 | CLANG_WARN_INT_CONVERSION = YES; 315 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 316 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 317 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 318 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 319 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 320 | CLANG_WARN_STRICT_PROTOTYPES = YES; 321 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 322 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 323 | CLANG_WARN_UNREACHABLE_CODE = YES; 324 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 325 | COPY_PHASE_STRIP = NO; 326 | DEBUG_INFORMATION_FORMAT = dwarf; 327 | ENABLE_STRICT_OBJC_MSGSEND = YES; 328 | ENABLE_TESTABILITY = YES; 329 | GCC_C_LANGUAGE_STANDARD = gnu11; 330 | GCC_DYNAMIC_NO_PIC = NO; 331 | GCC_NO_COMMON_BLOCKS = YES; 332 | GCC_OPTIMIZATION_LEVEL = 0; 333 | GCC_PREPROCESSOR_DEFINITIONS = ( 334 | "DEBUG=1", 335 | "$(inherited)", 336 | ); 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 344 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 345 | MTL_FAST_MATH = YES; 346 | ONLY_ACTIVE_ARCH = YES; 347 | SDKROOT = iphoneos; 348 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 349 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 350 | }; 351 | name = Debug; 352 | }; 353 | 7AE8EA1D24450758007B099F /* Release */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ALWAYS_SEARCH_USER_PATHS = NO; 357 | CLANG_ANALYZER_NONNULL = YES; 358 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 359 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 360 | CLANG_CXX_LIBRARY = "libc++"; 361 | CLANG_ENABLE_MODULES = YES; 362 | CLANG_ENABLE_OBJC_ARC = YES; 363 | CLANG_ENABLE_OBJC_WEAK = YES; 364 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 365 | CLANG_WARN_BOOL_CONVERSION = YES; 366 | CLANG_WARN_COMMA = YES; 367 | CLANG_WARN_CONSTANT_CONVERSION = YES; 368 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 369 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 370 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 371 | CLANG_WARN_EMPTY_BODY = YES; 372 | CLANG_WARN_ENUM_CONVERSION = YES; 373 | CLANG_WARN_INFINITE_RECURSION = YES; 374 | CLANG_WARN_INT_CONVERSION = YES; 375 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 376 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 377 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 378 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 379 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 380 | CLANG_WARN_STRICT_PROTOTYPES = YES; 381 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 382 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 383 | CLANG_WARN_UNREACHABLE_CODE = YES; 384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 385 | COPY_PHASE_STRIP = NO; 386 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 387 | ENABLE_NS_ASSERTIONS = NO; 388 | ENABLE_STRICT_OBJC_MSGSEND = YES; 389 | GCC_C_LANGUAGE_STANDARD = gnu11; 390 | GCC_NO_COMMON_BLOCKS = YES; 391 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 392 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 393 | GCC_WARN_UNDECLARED_SELECTOR = YES; 394 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 395 | GCC_WARN_UNUSED_FUNCTION = YES; 396 | GCC_WARN_UNUSED_VARIABLE = YES; 397 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 398 | MTL_ENABLE_DEBUG_INFO = NO; 399 | MTL_FAST_MATH = YES; 400 | SDKROOT = iphoneos; 401 | SWIFT_COMPILATION_MODE = wholemodule; 402 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 403 | VALIDATE_PRODUCT = YES; 404 | }; 405 | name = Release; 406 | }; 407 | 7AE8EA1F24450758007B099F /* Debug */ = { 408 | isa = XCBuildConfiguration; 409 | baseConfigurationReference = 39FE6FB41AB17FE3E297CFDD /* Pods-Stripe-SwiftUI.debug.xcconfig */; 410 | buildSettings = { 411 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 412 | CODE_SIGN_STYLE = Automatic; 413 | DEVELOPMENT_ASSET_PATHS = "\"Stripe-SwiftUI/Preview Content\""; 414 | DEVELOPMENT_TEAM = ZD9LN99SX6; 415 | ENABLE_PREVIEWS = YES; 416 | INFOPLIST_FILE = "Stripe-SwiftUI/Info.plist"; 417 | LD_RUNPATH_SEARCH_PATHS = ( 418 | "$(inherited)", 419 | "@executable_path/Frameworks", 420 | ); 421 | PRODUCT_BUNDLE_IDENTIFIER = "com.nelsongonzalez.Stripe-SwiftUI"; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | SWIFT_VERSION = 5.0; 424 | TARGETED_DEVICE_FAMILY = "1,2"; 425 | }; 426 | name = Debug; 427 | }; 428 | 7AE8EA2024450758007B099F /* Release */ = { 429 | isa = XCBuildConfiguration; 430 | baseConfigurationReference = 54E7F0501F36F760B85D39A7 /* Pods-Stripe-SwiftUI.release.xcconfig */; 431 | buildSettings = { 432 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 433 | CODE_SIGN_STYLE = Automatic; 434 | DEVELOPMENT_ASSET_PATHS = "\"Stripe-SwiftUI/Preview Content\""; 435 | DEVELOPMENT_TEAM = ZD9LN99SX6; 436 | ENABLE_PREVIEWS = YES; 437 | INFOPLIST_FILE = "Stripe-SwiftUI/Info.plist"; 438 | LD_RUNPATH_SEARCH_PATHS = ( 439 | "$(inherited)", 440 | "@executable_path/Frameworks", 441 | ); 442 | PRODUCT_BUNDLE_IDENTIFIER = "com.nelsongonzalez.Stripe-SwiftUI"; 443 | PRODUCT_NAME = "$(TARGET_NAME)"; 444 | SWIFT_VERSION = 5.0; 445 | TARGETED_DEVICE_FAMILY = "1,2"; 446 | }; 447 | name = Release; 448 | }; 449 | /* End XCBuildConfiguration section */ 450 | 451 | /* Begin XCConfigurationList section */ 452 | 7AE8EA0524450753007B099F /* Build configuration list for PBXProject "Stripe-SwiftUI" */ = { 453 | isa = XCConfigurationList; 454 | buildConfigurations = ( 455 | 7AE8EA1C24450758007B099F /* Debug */, 456 | 7AE8EA1D24450758007B099F /* Release */, 457 | ); 458 | defaultConfigurationIsVisible = 0; 459 | defaultConfigurationName = Release; 460 | }; 461 | 7AE8EA1E24450758007B099F /* Build configuration list for PBXNativeTarget "Stripe-SwiftUI" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | 7AE8EA1F24450758007B099F /* Debug */, 465 | 7AE8EA2024450758007B099F /* Release */, 466 | ); 467 | defaultConfigurationIsVisible = 0; 468 | defaultConfigurationName = Release; 469 | }; 470 | /* End XCConfigurationList section */ 471 | 472 | /* Begin XCRemoteSwiftPackageReference section */ 473 | 7A720DF92445E47B00F72E8C /* XCRemoteSwiftPackageReference "IQKeyboardManager" */ = { 474 | isa = XCRemoteSwiftPackageReference; 475 | repositoryURL = "https://github.com/hackiftekhar/IQKeyboardManager.git"; 476 | requirement = { 477 | kind = upToNextMajorVersion; 478 | minimumVersion = 6.5.5; 479 | }; 480 | }; 481 | /* End XCRemoteSwiftPackageReference section */ 482 | 483 | /* Begin XCSwiftPackageProductDependency section */ 484 | 7A720DFA2445E47B00F72E8C /* IQKeyboardManagerSwift */ = { 485 | isa = XCSwiftPackageProductDependency; 486 | package = 7A720DF92445E47B00F72E8C /* XCRemoteSwiftPackageReference "IQKeyboardManager" */; 487 | productName = IQKeyboardManagerSwift; 488 | }; 489 | /* End XCSwiftPackageProductDependency section */ 490 | }; 491 | rootObject = 7AE8EA0224450753007B099F /* Project object */; 492 | } 493 | -------------------------------------------------------------------------------- /Stripe-SwiftUI.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Stripe-SwiftUI.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Stripe-SwiftUI.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "IQKeyboardManagerSwift", 6 | "repositoryURL": "https://github.com/hackiftekhar/IQKeyboardManager.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "b0f0a127cca6bb39378447baa2afff74123abd09", 10 | "version": "6.5.5" 11 | } 12 | } 13 | ] 14 | }, 15 | "version": 1 16 | } 17 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Stripe-SwiftUI 4 | // 5 | // Created by Nelson Gonzalez on 4/13/20. 6 | // Copyright © 2020 Nelson Gonzalez. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Stripe 11 | import IQKeyboardManagerSwift 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate { 15 | 16 | 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 19 | // Override point for customization after application launch. 20 | 21 | IQKeyboardManager.shared.enable = true 22 | #warning("Please user your own Stripe Publishable key below") 23 | Stripe.setDefaultPublishableKey("pk_test_xxxxxxxxxxxxxxxxxxxxxx") 24 | return true 25 | } 26 | 27 | // MARK: UISceneSession Lifecycle 28 | 29 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 30 | // Called when a new scene session is being created. 31 | // Use this method to select a configuration to create the new scene with. 32 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 33 | } 34 | 35 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 36 | // Called when the user discards a scene session. 37 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 38 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 39 | } 40 | 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Assets.xcassets/Color.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.939", 9 | "green" : "0.930", 10 | "red" : "0.935" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Assets.xcassets/cake.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cake.jpg", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Assets.xcassets/cake.imageset/cake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelglez/stripe-swiftui/d03f0e2fe0c0da50aca39d60269e109bfaa250c8/Stripe-SwiftUI/Assets.xcassets/cake.imageset/cake.jpg -------------------------------------------------------------------------------- /Stripe-SwiftUI/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyView.swift 3 | // Stripe-SwiftUI 4 | // 5 | // Created by Nelson Gonzalez on 4/13/20. 6 | // Copyright © 2020 Nelson Gonzalez. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension String { 12 | public func toPhoneNumber() -> String { 13 | return self.replacingOccurrences(of: "(\\d{3})(\\d{3})(\\d+)", with: "($1) $2-$3", options: .regularExpression, range: nil) 14 | } 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Stripe-SwiftUI 4 | // 5 | // Created by Nelson Gonzalez on 4/13/20. 6 | // Copyright © 2020 Nelson Gonzalez. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let initialView = InitialView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: initialView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/StripeApi/MyApiClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MyApiClient.swift 3 | // Stripe-SwiftUI 4 | // 5 | // Created by Nelson Gonzalez on 4/13/20. 6 | // Copyright © 2020 Nelson Gonzalez. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Stripe 11 | class MyAPIClient: NSObject,STPCustomerEphemeralKeyProvider { 12 | 13 | #warning("Please use your own backend url below") 14 | static let baseUrl = "https://yourWebsite.com/StripeBackend/" 15 | 16 | 17 | 18 | func createCustomerKey(withAPIVersion apiVersion: String, completion: @escaping STPJSONResponseCompletionBlock) { 19 | 20 | //MARK: - Retrive customer that was saved on registering on the app. 21 | guard let customer = UserDefaults.standard.value(forKey: "Customer") as? String else { 22 | print("NO CUSTOMER SAVED") 23 | return 24 | } 25 | 26 | let createCustomerEndPoint = URL(string: MyAPIClient.baseUrl + "empheralkey.php") 27 | 28 | guard let url = createCustomerEndPoint else { 29 | print("The url is not valid.") 30 | return 31 | } 32 | 33 | let body = "api_version=\(apiVersion)&customer=\(customer)" 34 | 35 | var request = URLRequest(url: url) 36 | request.httpBody = body.data(using: .utf8) 37 | request.httpMethod = "POST" 38 | 39 | 40 | URLSession.shared.dataTask(with: request) { (data, response, error) in 41 | 42 | if let error = error { 43 | print(error.localizedDescription) 44 | completion(nil, error) 45 | return 46 | } 47 | 48 | guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else { 49 | print("Server Error!") 50 | completion(nil, NSError(domain: "empherakey.php", 51 | code: 100, 52 | userInfo: [NSLocalizedDescriptionKey: "Something went wrong"])) 53 | return 54 | } 55 | 56 | guard let data = data else { 57 | print("There is no data returned from request") 58 | completion(nil, NSError()) 59 | return 60 | } 61 | 62 | do { 63 | let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as! [String : Any] 64 | // print(json) 65 | print(json) 66 | 67 | completion(json, nil) 68 | 69 | 70 | 71 | } catch { 72 | print("JSON error: \(error.localizedDescription)") 73 | completion(nil, NSError()) 74 | return 75 | } 76 | 77 | }.resume() 78 | } 79 | 80 | //MARK: - STEP 1. Create customer. 81 | 82 | class func createCustomer(email: String, phone: String, name: String, onSuccess: @escaping() -> Void, onError: @escaping(Error) -> Void){ 83 | 84 | let createCustomerEndPoint = URL(string: baseUrl + "createCustomer.php") 85 | 86 | guard let url = createCustomerEndPoint else { 87 | print("The url is not valid.") 88 | return 89 | } 90 | 91 | let body = "email=\(email.lowercased())&phone=\(phone)&name=\(name)" 92 | 93 | var request = URLRequest(url: url) 94 | request.httpBody = body.data(using: .utf8) 95 | request.httpMethod = "POST" 96 | 97 | 98 | URLSession.shared.dataTask(with: request) { (data, response, error) in 99 | if let error = error { 100 | print(error.localizedDescription) 101 | onError(error) 102 | return 103 | } 104 | 105 | guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else { 106 | print("Server Error!") 107 | return 108 | } 109 | 110 | guard let data = data else { 111 | print("There is no data returned from request") 112 | onError(NSError()) 113 | return 114 | } 115 | 116 | do { 117 | let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as! [String : Any] 118 | // print(json) 119 | print(json) 120 | guard let customerId = json["id"] as? String else { 121 | print("Could not retrieve customerId") 122 | return 123 | 124 | } 125 | 126 | //MARK: - Save the new customer ID in our app. We will need this customerid later on. 127 | print("CustomerId: \(customerId)") 128 | UserDefaults.standard.set(customerId, forKey: "Customer") 129 | onSuccess() 130 | 131 | } catch { 132 | print("JSON error: \(error.localizedDescription)") 133 | onError(error) 134 | return 135 | } 136 | 137 | }.resume() 138 | 139 | } 140 | 141 | class func createPaymentIntent(amount: Int, currency: String, customerId: String, completion:@escaping (String) -> Void) { 142 | 143 | 144 | 145 | let createCustomerEndPoint = URL(string: MyAPIClient.baseUrl + "createpaymentintent.php") 146 | 147 | guard let url = createCustomerEndPoint else { 148 | print("The url is not valid.") 149 | return 150 | } 151 | 152 | let body = "amount=\(amount)¤cy=\(currency)&customerId=\(customerId)" 153 | 154 | var request = URLRequest(url: url) 155 | request.httpBody = body.data(using: .utf8) 156 | request.httpMethod = "POST" 157 | 158 | 159 | URLSession.shared.dataTask(with: request) { (data, response, error) in 160 | 161 | if let error = error { 162 | print(error.localizedDescription) 163 | completion(error.localizedDescription) 164 | return 165 | } 166 | 167 | guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else { 168 | print("Server Error!") 169 | completion("Server Response Error!") 170 | return 171 | } 172 | 173 | guard let data = data else { 174 | print("There is no data returned from request") 175 | completion("There is no data returned from request") 176 | return 177 | } 178 | 179 | do { 180 | let json = try JSONSerialization.jsonObject(with: data, options: [.allowFragments]) as? [String : String] 181 | // print(json) 182 | print(json!) 183 | guard let clientSecret = json?["clientSecret"] else { return } 184 | 185 | completion(clientSecret) 186 | 187 | 188 | 189 | } catch { 190 | print("JSON error: \(error)") 191 | completion(error.localizedDescription) 192 | return 193 | } 194 | 195 | }.resume() 196 | 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/StripeApi/PaymentContextDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PaymentContextDelegate.swift 3 | // Stripe-SwiftUI 4 | // 5 | // Created by Nelson Gonzalez on 4/14/20. 6 | // Copyright © 2020 Nelson Gonzalez. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Stripe 11 | import SwiftUI 12 | 13 | class PaymentContextDelegate: NSObject, STPPaymentContextDelegate, ObservableObject { 14 | 15 | @Published var paymentMethodButtonTitle = "Select Payment Method" 16 | @Published var showAlert = false 17 | @Published var message = "" 18 | 19 | func paymentContext(_ paymentContext: STPPaymentContext, didFinishWith status: STPPaymentStatus, error: Error?) { 20 | // let title: String 21 | var message: String 22 | 23 | switch status { 24 | case .success: 25 | 26 | // title = "Success!" 27 | message = "Thank you for your purchase." 28 | showAlert = true 29 | self.message = message 30 | case .error: 31 | 32 | // title = "Error" 33 | message = error?.localizedDescription ?? "" 34 | showAlert = true 35 | self.message = message 36 | case .userCancellation: 37 | return 38 | @unknown default: 39 | fatalError("Something really bad happened....") 40 | } 41 | } 42 | 43 | 44 | 45 | func paymentContextDidChange(_ paymentContext: STPPaymentContext) { 46 | 47 | paymentMethodButtonTitle = paymentContext.selectedPaymentOption?.label ?? "Select Payment Method" 48 | 49 | //updating the selected shipping method 50 | 51 | 52 | // shippingMethodButtonTitle = paymentContext.selectedShippingMethod?.label ?? "Select Shipping Method" 53 | // 54 | } 55 | 56 | func paymentContext(_ paymentContext: STPPaymentContext, didUpdateShippingAddress address: STPAddress, completion: @escaping STPShippingMethodsCompletionBlock) { 57 | // isSetShipping = false 58 | 59 | let upsGround = PKShippingMethod() 60 | upsGround.amount = 0 61 | upsGround.label = "UPS Ground" 62 | upsGround.detail = "Arrives in 3-5 days" 63 | upsGround.identifier = "ups_ground" 64 | 65 | let fedEx = PKShippingMethod() 66 | fedEx.amount = 5.99 67 | fedEx.label = "FedEx" 68 | fedEx.detail = "Arrives tomorrow" 69 | fedEx.identifier = "fedex" 70 | 71 | if address.country == "US" { 72 | completion(.valid, nil, [upsGround, fedEx], upsGround) 73 | } 74 | else { 75 | completion(.invalid, nil, nil, nil) 76 | } 77 | } 78 | 79 | func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) { 80 | 81 | } 82 | 83 | func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPPaymentStatusBlock) { 84 | 85 | guard let customerId = UserDefaults.standard.value(forKey: "Customer") as? String else { 86 | print("NO CUSTOMER SAVED") 87 | return 88 | } 89 | 90 | let paymentAmount = paymentContext.paymentAmount 91 | 92 | print("TOTAL: \(paymentAmount)") 93 | 94 | MyAPIClient.createPaymentIntent(amount: paymentAmount, currency: "usd", customerId: customerId) { (reponseString) in 95 | 96 | // Assemble the PaymentIntent parameters 97 | let paymentIntentParams = STPPaymentIntentParams(clientSecret: reponseString) 98 | paymentIntentParams.paymentMethodId = paymentResult.paymentMethod?.stripeId 99 | paymentIntentParams.paymentMethodParams = paymentResult.paymentMethodParams 100 | 101 | STPPaymentHandler.shared().confirmPayment(withParams: paymentIntentParams, authenticationContext: paymentContext) { status, paymentIntent, error in 102 | switch status { 103 | case .succeeded: 104 | // Your backend asynchronously fulfills the customer's order, e.g. via webhook 105 | print("SUCCESS!") 106 | 107 | completion(.success, nil) 108 | case .failed: 109 | completion(.error, error) // Report error 110 | case .canceled: 111 | completion(.userCancellation, nil) // Customer cancelled 112 | @unknown default: 113 | completion(.error, nil) 114 | } 115 | } 116 | 117 | } 118 | } 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/View Models/AboutYouViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AboutYouViewModel.swift 3 | // Stripe-SwiftUI 4 | // 5 | // Created by Nelson Gonzalez on 4/13/20. 6 | // Copyright © 2020 Nelson Gonzalez. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | class AboutYouViewModel: ObservableObject { 14 | @Published var fullName = "" 15 | @Published var email = "" 16 | @Published var phoneNumber = "" 17 | @Published var showAlert = false 18 | @Published var errorString = "" 19 | @Published var isRegistered = false 20 | 21 | func isRegisteredUser() { 22 | let user = UserDefaults.standard.value(forKey: "Customer") as? String 23 | if user != nil { 24 | DispatchQueue.main.async { 25 | self.isRegistered = true 26 | } 27 | 28 | } else { 29 | DispatchQueue.main.async { 30 | self.isRegistered = false 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Stripe-SwiftUI 4 | // 5 | // Created by Nelson Gonzalez on 4/13/20. 6 | // Copyright © 2020 Nelson Gonzalez. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | @ObservedObject var aboutYouViewModel: AboutYouViewModel 13 | 14 | var body: some View { 15 | 16 | VStack { 17 | Text("SwiftUI Stripe").font(.largeTitle) 18 | 19 | Text("About you:").font(.title) 20 | 21 | TextField("Full name", text: $aboutYouViewModel.fullName).padding().background(Color("Color")).clipShape(RoundedRectangle(cornerRadius: 10)).padding(.top, 1) 22 | 23 | TextField("124567890", text: $aboutYouViewModel.phoneNumber, onEditingChanged: { changed in 24 | 25 | self.aboutYouViewModel.phoneNumber = self.aboutYouViewModel.phoneNumber.toPhoneNumber() 26 | 27 | }).keyboardType(.phonePad).padding().background(Color("Color")).clipShape(RoundedRectangle(cornerRadius: 10)).padding(.top, 1) 28 | 29 | TextField("Email Address", text: $aboutYouViewModel.email).keyboardType(.emailAddress).padding().background(Color("Color")).clipShape(RoundedRectangle(cornerRadius: 10)).padding(.top, 1) 30 | 31 | 32 | Button(action: { 33 | if self.aboutYouViewModel.fullName.isEmpty, self.aboutYouViewModel.phoneNumber.isEmpty, self.aboutYouViewModel.email.isEmpty { 34 | //trigger alert 35 | print("please complete all fields.") 36 | self.aboutYouViewModel.showAlert = true 37 | self.aboutYouViewModel.errorString = "Please complete all fields to continue." 38 | return 39 | } else { 40 | MyAPIClient.createCustomer(email: self.aboutYouViewModel.email, phone: self.aboutYouViewModel.phoneNumber, name: self.aboutYouViewModel.fullName, onSuccess: { 41 | print("Successfully registered new user.") 42 | 43 | //MARK: - We need to trigger isRegistered so we can take the user to the shopping page of our app. 44 | 45 | self.aboutYouViewModel.isRegisteredUser() 46 | }) { (error) in 47 | self.aboutYouViewModel.showAlert = true 48 | self.aboutYouViewModel.errorString = error.localizedDescription 49 | } 50 | } 51 | }) { 52 | Text("Continue to app").frame(width: UIScreen.main.bounds.width - 30, height: 50) 53 | }.foregroundColor(.white).background(Color.orange).cornerRadius(10).padding(.top, 15).alert(isPresented: self.$aboutYouViewModel.showAlert) { 54 | Alert(title: Text("Error"), message: Text(self.aboutYouViewModel.errorString), dismissButton: .default(Text("OK"))) 55 | } 56 | 57 | Spacer() 58 | 59 | }.padding().navigationBarTitle("Stripe SwiftUI") 60 | 61 | } 62 | 63 | 64 | } 65 | 66 | struct ContentView_Previews: PreviewProvider { 67 | static var previews: some View { 68 | ContentView(aboutYouViewModel: AboutYouViewModel()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Views/InitialView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InititalView.swift 3 | // Stripe-SwiftUI 4 | // 5 | // Created by Nelson Gonzalez on 4/13/20. 6 | // Copyright © 2020 Nelson Gonzalez. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct InitialView: View { 12 | @ObservedObject var aboutYouViewModel = AboutYouViewModel() 13 | @ObservedObject var paymentContextDelegate = PaymentContextDelegate() 14 | func listen() { 15 | 16 | //MARK: - uncomment the line below to test as a brand new user 17 | // UserDefaults.standard.set(nil, forKey: "Customer") 18 | 19 | //MARK: - Check to see if there is a registered user or not 20 | aboutYouViewModel.isRegisteredUser() 21 | 22 | } 23 | 24 | var body: some View { 25 | NavigationView { 26 | Group { 27 | 28 | if aboutYouViewModel.isRegistered { 29 | //MARK: - if the user is registered then proceed to the shopping/checkout page 30 | ShoppingView(paymentContextDelegate: self.paymentContextDelegate) 31 | } else { 32 | //MARK: - if the user is not registered then take him to the login/signup view 33 | ContentView(aboutYouViewModel: self.aboutYouViewModel) 34 | } 35 | 36 | 37 | }.onAppear { 38 | //MARK: - check to see if the user is registered or not in our app. 39 | self.listen() 40 | 41 | } 42 | } 43 | } 44 | } 45 | 46 | struct InitialView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | InitialView() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Stripe-SwiftUI/Views/ShoppingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShoppingView.swift 3 | // Stripe-SwiftUI 4 | // 5 | // Created by Nelson Gonzalez on 4/13/20. 6 | // Copyright © 2020 Nelson Gonzalez. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Stripe 11 | 12 | 13 | 14 | struct ShoppingView: View { 15 | @ObservedObject var paymentContextDelegate: PaymentContextDelegate 16 | let config = STPPaymentConfiguration.shared() 17 | @State private var paymentContext: STPPaymentContext! 18 | 19 | 20 | let price = 20 21 | 22 | private let stripeCreditCartCut = 0.029 23 | private let flatFeeCents = 30 24 | 25 | 26 | var subtotal: Int { 27 | var amount = 0 28 | 29 | let priceToPennies = Int(price * 100) 30 | amount += priceToPennies 31 | 32 | 33 | return amount 34 | } 35 | 36 | var processingFees: Int { 37 | if subtotal == 0 { 38 | return 0 39 | } 40 | let sub = Double(subtotal) 41 | let feesAndSubtotal = Int(sub * stripeCreditCartCut) + flatFeeCents 42 | return feesAndSubtotal 43 | } 44 | 45 | var total: Int { 46 | return subtotal + processingFees 47 | } 48 | 49 | var body: some View { 50 | VStack(spacing: 20) { 51 | Image("cake").resizable().frame(width: 150, height: 150).cornerRadius(30) 52 | Text("Chocolate Cake").font(.title) 53 | Text("$\(price).00").foregroundColor(.green) 54 | 55 | //MARK: - present the payment options VC (to enter CC info) CC means credit card. 56 | Button(action: { 57 | 58 | self.paymentContext.presentPaymentOptionsViewController() 59 | }) { 60 | 61 | Text(self.paymentContextDelegate.paymentMethodButtonTitle) 62 | } 63 | 64 | //MARK: - If the user is new and has not selected a payment method yet, we dont show the Pay Now button until there is a CC on his account. CC means credit card. 65 | if self.paymentContextDelegate.paymentMethodButtonTitle != "Select Payment Method" { 66 | Button(action: { 67 | 68 | self.paymentContext.requestPayment() 69 | }) { 70 | Text("Pay Now").frame(width: UIScreen.main.bounds.width - 30, height: 50) 71 | }.foregroundColor(.white).background(Color.red).cornerRadius(10).padding(.top, 15) 72 | } 73 | 74 | Spacer() 75 | 76 | }.padding().navigationBarTitle("Order").onAppear { 77 | 78 | //MARK: - Start configuring the payment context as soon as the view appears 79 | 80 | self.paymentContextConfiguration() 81 | 82 | 83 | 84 | 85 | }.alert(isPresented: self.$paymentContextDelegate.showAlert) { 86 | Alert(title: Text(""), message: Text(self.paymentContextDelegate.message), dismissButton: .default(Text("OK"))) 87 | } 88 | } 89 | 90 | //MARK: - Configuration 91 | 92 | func paymentContextConfiguration() { 93 | let customerContext = STPCustomerContext(keyProvider: MyAPIClient()) 94 | self.config.shippingType = .shipping 95 | self.config.requiredBillingAddressFields = .full 96 | 97 | self.config.requiredShippingAddressFields = [.postalAddress, .emailAddress] 98 | 99 | self.config.companyName = "Testing" 100 | 101 | self.paymentContext = STPPaymentContext(customerContext: customerContext, configuration: self.config, theme: .default()) 102 | 103 | self.paymentContext.delegate = self.paymentContextDelegate 104 | 105 | let keyWindow = UIApplication.shared.connectedScenes 106 | .filter({$0.activationState == .foregroundActive}) 107 | .map({$0 as? UIWindowScene}) 108 | .compactMap({$0}) 109 | .first?.windows 110 | .filter({$0.isKeyWindow}).first 111 | 112 | self.paymentContext.hostViewController = keyWindow?.rootViewController 113 | self.paymentContext.paymentAmount = self.total 114 | } 115 | } 116 | 117 | struct ShoppingView_Previews: PreviewProvider { 118 | static var previews: some View { 119 | ShoppingView(paymentContextDelegate: PaymentContextDelegate()) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /image/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelglez/stripe-swiftui/d03f0e2fe0c0da50aca39d60269e109bfaa250c8/image/ss1.png -------------------------------------------------------------------------------- /image/ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelglez/stripe-swiftui/d03f0e2fe0c0da50aca39d60269e109bfaa250c8/image/ss2.png -------------------------------------------------------------------------------- /image/ss3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelglez/stripe-swiftui/d03f0e2fe0c0da50aca39d60269e109bfaa250c8/image/ss3.png -------------------------------------------------------------------------------- /image/ss4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelglez/stripe-swiftui/d03f0e2fe0c0da50aca39d60269e109bfaa250c8/image/ss4.png -------------------------------------------------------------------------------- /image/ss5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelglez/stripe-swiftui/d03f0e2fe0c0da50aca39d60269e109bfaa250c8/image/ss5.png -------------------------------------------------------------------------------- /image/ss6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelglez/stripe-swiftui/d03f0e2fe0c0da50aca39d60269e109bfaa250c8/image/ss6.png -------------------------------------------------------------------------------- /image/ss7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nelglez/stripe-swiftui/d03f0e2fe0c0da50aca39d60269e109bfaa250c8/image/ss7.png --------------------------------------------------------------------------------