diff --git a/.github/workflows/SonarCloud.yml b/.github/workflows/SonarCloud.yml new file mode 100644 index 0000000..67f68e1 --- /dev/null +++ b/.github/workflows/SonarCloud.yml @@ -0,0 +1,45 @@ +name: SonarCloud +on: + schedule: + - cron: '0 0 * * *' + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: +jobs: + sonarcloud: + name: SonarCloud + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Setup environment + run: | + bundle install + - name: Build + shell: bash + run: | + curl -L -O https://sonarcloud.io/static/cpp/build-wrapper-macosx-x86.zip + unzip -o build-wrapper-macosx-x86.zip + /Users/runner/work/paystack-ios/paystack-ios/build-wrapper-macosx-x86/build-wrapper-macosx-x86 --out-dir DerivedData\compilation-database \ + xcodebuild \ + -scheme "PaystackiOS Tests"\ + -derivedDataPath DerivedData\ + -enableCodeCoverage "YES"\ + -destination 'platform=iOS Simulator,name=iPhone 8,OS=14.4'\ + test + + - name: Set up JDK 17 + uses: actions/setup-java@v3.10.0 + with: + distribution: 'temurin' + java-version: 17 + + - name: SonarCloud Analysis + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + gem install slather + brew install sonar-scanner + bundle exec fastlane metrics diff --git a/.github/workflows/deploy_to_cocoapods.yml b/.github/workflows/deploy_to_cocoapods.yml new file mode 100644 index 0000000..a3f6923 --- /dev/null +++ b/.github/workflows/deploy_to_cocoapods.yml @@ -0,0 +1,19 @@ +name: Deploy to Cocoapods Trunk + +on: + push: + tags: + - '*' + workflow_dispatch: + +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - name: Install Cocoapods + run: gem install cocoapods + + - uses: michaelhenry/deploy-to-cocoapods-github-action@1.0.10 + env: + COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }} diff --git a/.github/workflows/primary.yml b/.github/workflows/primary.yml new file mode 100644 index 0000000..fee6942 --- /dev/null +++ b/.github/workflows/primary.yml @@ -0,0 +1,39 @@ +name: Primary +on: + pull_request: + branches: [ master ] + +jobs: + CodeQuality: + runs-on: ubuntu-latest + name: Code quality Checks + + steps: + - uses: actions/checkout@v3 + - name: Danger + uses: docker://ghcr.io/danger/danger-swift-with-swiftlint:3.9.1 + with: + args: --failOnErrors --no-publish-check + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + UnitTests: + name: Unit Tests + runs-on: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup environment + run: | + bundle install + + - name: Select Xcode Version + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: latest-stable + + - name: Run tests + run: | + bundle exec fastlane unit_tests diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..a0d1224 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,5 @@ +disabled_rules: # rule identifiers turned on by default to exclude from running + - line_length + - force_cast +excluded: # paths to ignore during linting. Takes precedence over `included`. + - Example/Pods diff --git a/BuildConfigurations/PaystackOSX-Debug.xcconfig b/BuildConfigurations/PaystackOSX-Debug.xcconfig new file mode 100644 index 0000000..5556711 --- /dev/null +++ b/BuildConfigurations/PaystackOSX-Debug.xcconfig @@ -0,0 +1,11 @@ +// +// PaystackOSX-Debug.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +#include "PaystackOSX-Shared.xcconfig" + + +GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 $(inherited) \ No newline at end of file diff --git a/BuildConfigurations/PaystackOSX-Release.xcconfig b/BuildConfigurations/PaystackOSX-Release.xcconfig new file mode 100644 index 0000000..48f2d46 --- /dev/null +++ b/BuildConfigurations/PaystackOSX-Release.xcconfig @@ -0,0 +1,22 @@ +// +// PaystackOSX-Release.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +#include "PaystackOSX-Shared.xcconfig" + + +// Debug Information Format +// +// This setting controls the format of debug information used by the developer tools. +// +// DWARF - Object files and linked products will use DWARF as the debug information +// format. [dwarf] +// DWARF with dSYM File - Object files and linked products will use DWARF as the debug +// information format, and Xcode will also produce a dSYM file containing the debug +// information from the individual object files (except that a dSYM file is not needed +// and will not be created for static library or object file products). [dwarf-with-dsym] + +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym \ No newline at end of file diff --git a/BuildConfigurations/PaystackOSX-Shared.xcconfig b/BuildConfigurations/PaystackOSX-Shared.xcconfig new file mode 100644 index 0000000..130d777 --- /dev/null +++ b/BuildConfigurations/PaystackOSX-Shared.xcconfig @@ -0,0 +1,186 @@ +// +// PaystackOSX-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + + +CLANG_CXX_LANGUAGE_STANDARD = gnu++0x + + +CLANG_CXX_LIBRARY = libc++ + + +CLANG_ENABLE_MODULES = YES + + +CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR + + +CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR + + +// Combine High Resolution Artwork +// +// Combines image files at different resolutions into one multi-page TIFF file that is +// HiDPI compliant for Mac OS X 10.7 and later. Only image files in the same directory +// and with the same base name and extension are combined. The file names must conform to +// the naming convention used in HiDPI. + +COMBINE_HIDPI_IMAGES = YES + + +// Current Project Version +// +// This setting defines the the current version of the project. The value must be a +// integer or floating point number like 57 or 365.8. + +CURRENT_PROJECT_VERSION = 1 + + +// Defines Module +// +// If enabled, the product will be treated as defining its own module. This enables +// automatic production of LLVM module map files when appropriate, and allows the product +// to be imported as a module. + +DEFINES_MODULE = YES + +MODULEMAP_FILE = Paystack/module.modulemap + + +// Compatibility Version +// +// Determines the compatibility version of the resulting library, bundle, or framework +// binary. + +DYLIB_COMPATIBILITY_VERSION = 1 + + +// Current Library Version +// +// This setting defines the the current version of any framework built by the project. +// Like "Current Project Version", the value must be an integer or floating point number +// like 57 or 365.8. By default it is set to $(CURRENT_PROJECT_VERSION). + +DYLIB_CURRENT_VERSION = 1 + + +// Dynamic Library Install Name Base +// +// Sets the base value for the internal "install path" (LC_ID_DYLIB) in a dynamic +// library. This will be combined with the EXECUTABLE_PATH to form the full install path. +// Setting LD_DYLIB_INSTALL_NAME directly will override this setting. This setting +// defaults to the target's INSTALL_PATH. It is ignored when building any product other +// than a dynamic library. [-install_name] + +DYLIB_INSTALL_NAME_BASE = @rpath + + +// Framework Version +// +// Framework bundles are versioned by having contents in subfolders of a version folder +// that has links to the current version and its contents. + +FRAMEWORK_VERSION = A + + +GCC_TREAT_WARNINGS_AS_ERRORS = YES + + +GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR + + +GCC_WARN_SHADOW = YES + + +GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE + + +// Info.plist File +// +// This is the project-relative path to the plist file that contains the Info.plist +// information used by bundles. + +INFOPLIST_FILE = Paystack/Info.plist + + +// Installation Directory +// +// The directory to install the build products in. This path is prepended by the +// 'Installation Build Products Location' (i.e., $(DSTROOT)). + +INSTALL_PATH = $(LOCAL_LIBRARY_DIR)/Frameworks + + +// Runpath Search Paths +// +// This is a list of paths to be added to the runpath search path list for the image +// being created. At runtime, dyld uses the runpath when searching for dylibs whose load +// path begins with '@rpath/'. [-rpath] + +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks + + +// OS X Deployment Target +// +// Code will load on this and later versions of OS X. Framework APIs that are +// unavailable in earlier versions will be weak-linked; your code should check for null +// function pointers or specific system versions before calling newer APIs. +// +// Compiler Default - Code will load on any Mac OS system that supports the APIs that are +// used. +// OS X 10.4 - Code will not load on systems earlier than 10.4. [10.4] +// OS X 10.5 - Code will not load on systems earlier than 10.5. [10.5] +// OS X 10.6 - Code will not load on systems earlier than 10.6. [10.6] +// OS X 10.7 - Code will not load on systems earlier than 10.7. [10.7] +// OS X 10.8 - Code will not load on systems earlier than 10.8. [10.8] +// OS X 10.9 - Code will not load on systems earlier than 10.9. [10.9] + +MACOSX_DEPLOYMENT_TARGET = 10.9 + + +// Product Name +// +// This is the basename of the product generated. + +PRODUCT_NAME = PaystackOSX + + +// Base SDK +// +// The name or path of the base SDK being used during the build. The product will be +// built against the headers and libraries located inside the indicated SDK. This path +// will be prepended to all search paths, and will be passed through the environment to +// the compiler and linker. Additional SDKs can be specified in the ADDITIONAL_SDKS +// setting. + +SDKROOT = macosx + + +// Skip Install +// +// Activating this setting when deployment locations are used causes the product to be +// built into an alternative location instead of the install location. + +SKIP_INSTALL = YES + + +// Versioning Name Prefix +// +// Used as a prefix for the name of the version info symbol in the generated versioning +// source file. If you prefix your exported symbols you will probably want to set this +// to the same prefix. + +VERSION_INFO_PREFIX = + + +// Versioning System +// +// Selects the process used for version-stamping generated files. +// +// None - Use no versioning system. [] +// Apple Generic - Use the current project version setting. [apple-generic] + +VERSIONING_SYSTEM = apple-generic diff --git a/BuildConfigurations/PaystackOSXTests-Debug.xcconfig b/BuildConfigurations/PaystackOSXTests-Debug.xcconfig new file mode 100644 index 0000000..e6b6317 --- /dev/null +++ b/BuildConfigurations/PaystackOSXTests-Debug.xcconfig @@ -0,0 +1,11 @@ +// +// PaystackOSXTests-Debug.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +#include "PaystackOSXTests-Shared.xcconfig" + + +GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 $(inherited) \ No newline at end of file diff --git a/BuildConfigurations/PaystackOSXTests-Release.xcconfig b/BuildConfigurations/PaystackOSXTests-Release.xcconfig new file mode 100644 index 0000000..afa8f3e --- /dev/null +++ b/BuildConfigurations/PaystackOSXTests-Release.xcconfig @@ -0,0 +1,22 @@ +// +// PaystackOSXTests-Release.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +#include "PaystackOSXTests-Shared.xcconfig" + + +// Debug Information Format +// +// This setting controls the format of debug information used by the developer tools. +// +// DWARF - Object files and linked products will use DWARF as the debug information +// format. [dwarf] +// DWARF with dSYM File - Object files and linked products will use DWARF as the debug +// information format, and Xcode will also produce a dSYM file containing the debug +// information from the individual object files (except that a dSYM file is not needed +// and will not be created for static library or object file products). [dwarf-with-dsym] + +DEBUG_INFORMATION_FORMAT = dwarf-with-dsym diff --git a/BuildConfigurations/PaystackOSXTests-Shared.xcconfig b/BuildConfigurations/PaystackOSXTests-Shared.xcconfig new file mode 100644 index 0000000..dc3d9dd --- /dev/null +++ b/BuildConfigurations/PaystackOSXTests-Shared.xcconfig @@ -0,0 +1,122 @@ +// +// PaystackOSXTests-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + + +CLANG_CXX_LANGUAGE_STANDARD = gnu++0x + + + +CLANG_CXX_LIBRARY = libc++ + + + +CLANG_ENABLE_MODULES = YES + + + +CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR + + + +CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR + + + +// Combine High Resolution Artwork +// +// Combines image files at different resolutions into one multi-page TIFF file that is +// HiDPI compliant for Mac OS X 10.7 and later. Only image files in the same directory +// and with the same base name and extension are combined. The file names must conform to +// the naming convention used in HiDPI. + +COMBINE_HIDPI_IMAGES = YES + + + +// Framework Search Paths +// +// This is a list of paths to folders containing frameworks to be searched by the +// compiler for both included or imported header files when compiling C, Objective-C, +// C++, or Objective-C++, and by the linker for frameworks used by the product. Paths are +// delimited by whitespace, so any paths with spaces in them need to be properly quoted. +// [-F] + +FRAMEWORK_SEARCH_PATHS = $(DEVELOPER_FRAMEWORKS_DIR) $(inherited) + + + +GCC_TREAT_WARNINGS_AS_ERRORS = YES + + + +GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR + + + +GCC_WARN_SHADOW = YES + + + +GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE + + + +// Info.plist File +// +// This is the project-relative path to the plist file that contains the Info.plist +// information used by bundles. + +INFOPLIST_FILE = Tests/Tests/Info.plist + + + +// Runpath Search Paths +// +// This is a list of paths to be added to the runpath search path list for the image +// being created. At runtime, dyld uses the runpath when searching for dylibs whose load +// path begins with '@rpath/'. [-rpath] + +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/../Frameworks @loader_path/../Frameworks + + + +// OS X Deployment Target +// +// Code will load on this and later versions of OS X. Framework APIs that are +// unavailable in earlier versions will be weak-linked; your code should check for null +// function pointers or specific system versions before calling newer APIs. +// +// Compiler Default - Code will load on any Mac OS system that supports the APIs that are +// used. +// OS X 10.4 - Code will not load on systems earlier than 10.4. [10.4] +// OS X 10.5 - Code will not load on systems earlier than 10.5. [10.5] +// OS X 10.6 - Code will not load on systems earlier than 10.6. [10.6] +// OS X 10.7 - Code will not load on systems earlier than 10.7. [10.7] +// OS X 10.8 - Code will not load on systems earlier than 10.8. [10.8] +// OS X 10.9 - Code will not load on systems earlier than 10.9. [10.9] + +MACOSX_DEPLOYMENT_TARGET = 10.10 + + + +// Product Name +// +// This is the basename of the product generated. + +PRODUCT_NAME = $(TARGET_NAME) + + + +// Base SDK +// +// The name or path of the base SDK being used during the build. The product will be +// built against the headers and libraries located inside the indicated SDK. This path +// will be prepended to all search paths, and will be passed through the environment to +// the compiler and linker. Additional SDKs can be specified in the ADDITIONAL_SDKS +// setting. + +SDKROOT = macosx \ No newline at end of file diff --git a/BuildConfigurations/PaystackiOS Tests-Debug.xcconfig b/BuildConfigurations/PaystackiOS Tests-Debug.xcconfig new file mode 100644 index 0000000..bdd1b63 --- /dev/null +++ b/BuildConfigurations/PaystackiOS Tests-Debug.xcconfig @@ -0,0 +1,11 @@ +// +// PaystackiOS Tests-Debug.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +#include "PaystackiOS Tests-Shared.xcconfig" + +GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 $(inherited) +DEBUG_INFORMATION_FORMAT = dwarf diff --git a/BuildConfigurations/PaystackiOS Tests-Release.xcconfig b/BuildConfigurations/PaystackiOS Tests-Release.xcconfig new file mode 100644 index 0000000..f29f324 --- /dev/null +++ b/BuildConfigurations/PaystackiOS Tests-Release.xcconfig @@ -0,0 +1,12 @@ +// +// PaystackiOS Tests-Release.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +#include "PaystackiOS Tests-Shared.xcconfig" + +//********************************************// +//* Currently no build settings in this file *// +//********************************************// diff --git a/BuildConfigurations/PaystackiOS Tests-Shared.xcconfig b/BuildConfigurations/PaystackiOS Tests-Shared.xcconfig new file mode 100644 index 0000000..c2b1731 --- /dev/null +++ b/BuildConfigurations/PaystackiOS Tests-Shared.xcconfig @@ -0,0 +1,92 @@ +// +// PaystackiOS Tests-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + + +CLANG_CXX_LANGUAGE_STANDARD = gnu++0x + + +CLANG_CXX_LIBRARY = libc++ + + +CLANG_ENABLE_MODULES = YES + + +CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR + + +CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR + + +IPHONEOS_DEPLOYMENT_TARGET = 8.0 + + +// Code Signing Identity +// +// The name ("common name") of a valid code-signing certificate in a keychain within your +// keychain path. A missing or invalid certificate will cause a build error. + +CODE_SIGN_IDENTITY = iPhone Developer + + +// Framework Search Paths +// +// This is a list of paths to folders containing frameworks to be searched by the +// compiler for both included or imported header files when compiling C, Objective-C, +// C++, or Objective-C++, and by the linker for frameworks used by the product. Paths are +// delimited by whitespace, so any paths with spaces in them need to be properly quoted. +// [-F] + +FRAMEWORK_SEARCH_PATHS = $(inherited) + + +GCC_TREAT_WARNINGS_AS_ERRORS = YES + + +GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR + + +GCC_WARN_SHADOW = YES + + +GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE + + +// Header Search Paths +// +// This is a list of paths to folders to be searched by the compiler for included or +// imported header files when compiling C, Objective-C, C++, or Objective-C++. Paths are +// delimited by whitespace, so any paths with spaces in them need to be properly quoted. +// [-I] + +HEADER_SEARCH_PATHS = $(inherited) + + +// Info.plist File +// +// This is the project-relative path to the plist file that contains the Info.plist +// information used by bundles. + +INFOPLIST_FILE = Tests/Tests/Info.plist + + +// Runpath Search Paths +// +// This is a list of paths to be added to the runpath search path list for the image +// being created. At runtime, dyld uses the runpath when searching for dylibs whose load +// path begins with '@rpath/'. [-rpath] + +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks + + +OTHER_CFLAGS = + + +// Product Name +// +// This is the basename of the product generated. + +PRODUCT_NAME = $(TARGET_NAME) diff --git a/BuildConfigurations/PaystackiOS-Debug.xcconfig b/BuildConfigurations/PaystackiOS-Debug.xcconfig new file mode 100644 index 0000000..0aa4115 --- /dev/null +++ b/BuildConfigurations/PaystackiOS-Debug.xcconfig @@ -0,0 +1,11 @@ +// +// PaystackiOS-Debug.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +#include "PaystackiOS-Shared.xcconfig" + + +GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 $(inherited) diff --git a/BuildConfigurations/PaystackiOS-Release.xcconfig b/BuildConfigurations/PaystackiOS-Release.xcconfig new file mode 100644 index 0000000..06440f6 --- /dev/null +++ b/BuildConfigurations/PaystackiOS-Release.xcconfig @@ -0,0 +1,10 @@ +// +// PaystackiOS-Release.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +#include "PaystackiOS-Shared.xcconfig" + +ENABLE_NS_ASSERTIONS = YES diff --git a/BuildConfigurations/PaystackiOS-Shared.xcconfig b/BuildConfigurations/PaystackiOS-Shared.xcconfig new file mode 100644 index 0000000..da2d697 --- /dev/null +++ b/BuildConfigurations/PaystackiOS-Shared.xcconfig @@ -0,0 +1,167 @@ +// +// PaystackiOS-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + + +CLANG_CXX_LANGUAGE_STANDARD = gnu++0x + + +CLANG_CXX_LIBRARY = libc++ + + +CLANG_ENABLE_MODULES = YES + + +CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR + + +CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR + + +// Code Signing Identity +// +// The name ("common name") of a valid code-signing certificate in a keychain within your +// keychain path. A missing or invalid certificate will cause a build error. + +CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer + + +// Current Project Version +// +// This setting defines the the current version of the project. The value must be a +// integer or floating point number like 57 or 365.8. + +CURRENT_PROJECT_VERSION = 1 + + +// Defines Module +// +// If enabled, the product will be treated as defining its own module. This enables +// automatic production of LLVM module map files when appropriate, and allows the product +// to be imported as a module. + +DEFINES_MODULE = YES + + +// Compatibility Version +// +// Determines the compatibility version of the resulting library, bundle, or framework +// binary. + +DYLIB_COMPATIBILITY_VERSION = 1 + + +// Current Library Version +// +// This setting defines the the current version of any framework built by the project. +// Like "Current Project Version", the value must be an integer or floating point number +// like 57 or 365.8. By default it is set to $(CURRENT_PROJECT_VERSION). + +DYLIB_CURRENT_VERSION = 1 + + +// Dynamic Library Install Name Base +// +// Sets the base value for the internal "install path" (LC_ID_DYLIB) in a dynamic +// library. This will be combined with the EXECUTABLE_PATH to form the full install path. +// Setting LD_DYLIB_INSTALL_NAME directly will override this setting. This setting +// defaults to the target's INSTALL_PATH. It is ignored when building any product other +// than a dynamic library. [-install_name] + +DYLIB_INSTALL_NAME_BASE = @rpath + + +GCC_TREAT_WARNINGS_AS_ERRORS = YES + + +GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR + + +GCC_WARN_SHADOW = YES + + +GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE + + +// Info.plist File +// +// This is the project-relative path to the plist file that contains the Info.plist +// information used by bundles. + +INFOPLIST_FILE = Paystack/Info.plist + + +// Installation Directory +// +// The directory to install the build products in. This path is prepended by the +// 'Installation Build Products Location' (i.e., $(DSTROOT)). + +INSTALL_PATH = $(LOCAL_LIBRARY_DIR)/Frameworks + + +// iOS Deployment Target +// +// Code will load on this and later versions of iOS. Framework APIs that are unavailable +// in earlier versions will be weak-linked; your code should check for null function +// pointers or specific system versions before calling newer APIs. +// + +IPHONEOS_DEPLOYMENT_TARGET = 8.0 + + +// Runpath Search Paths +// +// This is a list of paths to be added to the runpath search path list for the image +// being created. At runtime, dyld uses the runpath when searching for dylibs whose load +// path begins with '@rpath/'. [-rpath] + +LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks + + +// Product Name +// +// This is the basename of the product generated. + +PRODUCT_NAME = Paystack + + +// Skip Install +// +// Activating this setting when deployment locations are used causes the product to be +// built into an alternative location instead of the install location. + +SKIP_INSTALL = YES + + +// Targeted Device Family +// +// The build system uses the selected device to set the correct value for the +// UIDeviceFamily key it adds to the target's Info.plist file. +// +// iPhone - Application is built for iPhone and iPod touch. +// iPad - Application is built for iPad. +// iPhone/iPad - Application is built Universal for iPhone, iPod touch, and iPad. + +TARGETED_DEVICE_FAMILY = 1,2 + + +// Versioning Name Prefix +// +// Used as a prefix for the name of the version info symbol in the generated versioning +// source file. If you prefix your exported symbols you will probably want to set this +// to the same prefix. + +VERSION_INFO_PREFIX = + + +// Versioning System +// +// Selects the process used for version-stamping generated files. +// +// None - Use no versioning system. [] +// Apple Generic - Use the current project version setting. [apple-generic] + +VERSIONING_SYSTEM = apple-generic diff --git a/BuildConfigurations/PaystackiOSStatic.xcconfig b/BuildConfigurations/PaystackiOSStatic.xcconfig new file mode 100644 index 0000000..6f61b69 --- /dev/null +++ b/BuildConfigurations/PaystackiOSStatic.xcconfig @@ -0,0 +1,99 @@ +// +// PaystackiOSStatic-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +// Strip Debug Symbols During Copy +// +// Activating this setting causes binary files which are copied during the build (e.g., +// in a Copy Bundle Resources or Copy Files build phase) to be stripped of debugging +// symbols. It does not cause the linked product of a target to be stripped (use Strip +// Linked Product for that). + +COPY_PHASE_STRIP = NO + +CLANG_CXX_LANGUAGE_STANDARD = gnu++0x + + +CLANG_CXX_LIBRARY = libc++ + + +CLANG_ENABLE_MODULES = YES + + +CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR + + +CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR + + +// Dead Code Stripping +// +// Activating this setting causes the -dead_strip flag to be passed to ld(1) via cc(1) to +// turn on dead code stripping. If this option is selected, -gfull (not -gused) must be +// used to generate debugging symbols in order to have them correctly stripped. +// [-dead_strip] + +DEAD_CODE_STRIPPING = NO + + +GCC_TREAT_WARNINGS_AS_ERRORS = YES + + +GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR + + +GCC_WARN_SHADOW = YES + + +GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE + +PSTCK_EXTRA_PREPROCESSOR_MACROS = PSTCK_STATIC_LIBRARY_BUILD + +// iOS Deployment Target +// +// Code will load on this and later versions of iOS. Framework APIs that are unavailable +// in earlier versions will be weak-linked; your code should check for null function +// pointers or specific system versions before calling newer APIs. +// + +IPHONEOS_DEPLOYMENT_TARGET = 7.0 + + +// Other Linker Flags +// +// Options defined in this setting are passed to invocations of the linker. + +OTHER_LDFLAGS = -ObjC + + +// Product Name +// +// This is the basename of the product generated. + +PRODUCT_NAME = Paystack + + +// Skip Install +// +// Activating this setting when deployment locations are used causes the product to be +// built into an alternative location instead of the install location. + +SKIP_INSTALL = YES + + +// Strip Style +// +// Defines the level of symbol stripping to be performed on the linked product of the +// build. The default value is defined by the target's product type. +// +// All Symbols - Completely strips the binary, removing the symbol table and relocation +// information. [all, -s] +// Non-Global Symbols - Strips non-global symbols, but saves external symbols. +// [non-global, -x] +// Debugging Symbols - Strips debugging symbols, but saves local and global symbols. +// [debugging, -S] + +STRIP_STYLE = non-global diff --git a/BuildConfigurations/PaystackiOSStaticFramework.xcconfig b/BuildConfigurations/PaystackiOSStaticFramework.xcconfig new file mode 100644 index 0000000..a7c222c --- /dev/null +++ b/BuildConfigurations/PaystackiOSStaticFramework.xcconfig @@ -0,0 +1,19 @@ +// +// PaystackiOSStaticFramework-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + + +GCC_TREAT_WARNINGS_AS_ERRORS = YES + + +GCC_WARN_SHADOW = YES + + +// Product Name +// +// This is the basename of the product generated. + +PRODUCT_NAME = $(TARGET_NAME) diff --git a/BuildConfigurations/Project-Debug.xcconfig b/BuildConfigurations/Project-Debug.xcconfig new file mode 100644 index 0000000..919c77d --- /dev/null +++ b/BuildConfigurations/Project-Debug.xcconfig @@ -0,0 +1,48 @@ +// +// Project-Debug.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +#include "Project-Shared.xcconfig" + + +// Strip Debug Symbols During Copy +// +// Activating this setting causes binary files which are copied during the build (e.g., +// in a Copy Bundle Resources or Copy Files build phase) to be stripped of debugging +// symbols. It does not cause the linked product of a target to be stripped (use Strip +// Linked Product for that). + +COPY_PHASE_STRIP = NO + + +GCC_DYNAMIC_NO_PIC = NO + + +GCC_OPTIMIZATION_LEVEL = 0 + + +PSTCK_EXTRA_PREPROCESSOR_MACROS= +GCC_PREPROCESSOR_DEFINITIONS = DEBUG=1 $(inherited) $(PSTCK_EXTRA_PREPROCESSOR_MACROS) + + +GCC_SYMBOLS_PRIVATE_EXTERN = NO + + +// Build Active Architecture Only +// +// When checked, only the active architecture is built for faster debugging turnaround + +ONLY_ACTIVE_ARCH = YES + +// iOS Deployment Target +// +// Code will load on this and later versions of iOS. Framework APIs that are unavailable +// in earlier versions will be weak-linked; your code should check for null function +// pointers or specific system versions before calling newer APIs. + +IPHONEOS_DEPLOYMENT_TARGET = 7.0 + +ENABLE_TESTABILITY = YES diff --git a/BuildConfigurations/Project-Release.xcconfig b/BuildConfigurations/Project-Release.xcconfig new file mode 100644 index 0000000..483967d --- /dev/null +++ b/BuildConfigurations/Project-Release.xcconfig @@ -0,0 +1,41 @@ +// +// Project-Release.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + +#include "Project-Shared.xcconfig" + + +// Strip Debug Symbols During Copy +// +// Activating this setting causes binary files which are copied during the build (e.g., +// in a Copy Bundle Resources or Copy Files build phase) to be stripped of debugging +// symbols. It does not cause the linked product of a target to be stripped (use Strip +// Linked Product for that). + +COPY_PHASE_STRIP = YES + + +ENABLE_NS_ASSERTIONS = NO + +PSTCK_EXTRA_PREPROCESSOR_MACROS= +GCC_PREPROCESSOR_DEFINITIONS = NDEBUG $(PSTCK_EXTRA_PREPROCESSOR_MACROS) + + +// Validate Built Product +// +// Activating this setting will cause Xcode to perform validation checks on the product +// as part of the build process. + +VALIDATE_PRODUCT = YES + +// iOS Deployment Target +// +// Code will load on this and later versions of iOS. Framework APIs that are unavailable +// in earlier versions will be weak-linked; your code should check for null function +// pointers or specific system versions before calling newer APIs. +// + +IPHONEOS_DEPLOYMENT_TARGET = 8.0 diff --git a/BuildConfigurations/Project-Shared.xcconfig b/BuildConfigurations/Project-Shared.xcconfig new file mode 100644 index 0000000..39bbea8 --- /dev/null +++ b/BuildConfigurations/Project-Shared.xcconfig @@ -0,0 +1,130 @@ +// +// Project-Shared.xcconfig +// +// Generated by BuildSettingExtractor on 4/27/15 +// https://github.com/dempseyatgithub/BuildSettingExtractor +// + + +// Always Search User Paths +// +// If enabled both #include -style and #include "header.h"-style directives +// will search the paths in "User Header Search Paths" before "Header Search Paths", with +// the consequence that user headers (such as your own String.h header) would have +// precedence over system headers when using #include . This is done using the +// -iquote flag for the paths provided in "User Header Search Paths". If disabled and +// your compiler fully supports separate user paths, user headers will only be accessible +// with #include "header.h"-style preprocessor directives. +// +// For backwards compatibility reasons, this setting is enabled by default, but disabling +// it is strongly recommended. + +ALWAYS_SEARCH_USER_PATHS = NO + + +CLANG_ENABLE_OBJC_ARC = YES + + +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES + + +CLANG_WARN_BOOL_CONVERSION = YES + + +CLANG_WARN_CONSTANT_CONVERSION = YES + + +CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES + + +CLANG_WARN_EMPTY_BODY = YES + + +CLANG_WARN_ENUM_CONVERSION = YES + + +CLANG_WARN_INT_CONVERSION = YES + + +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES + + +CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES + + +CLANG_WARN_UNREACHABLE_CODE = YES + + +ENABLE_STRICT_OBJC_MSGSEND = YES + + +GCC_C_LANGUAGE_STANDARD = gnu99 + + +// Compiler for C/C++/Objective-C +// +// The compiler to use for C, C++, and Objective-C. + +GCC_VERSION = com.apple.compilers.llvm.clang.1_0 + + +GCC_WARN_64_TO_32_BIT_CONVERSION = YES + + +GCC_WARN_ABOUT_MISSING_NEWLINE = YES + + +GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES + + +GCC_WARN_ABOUT_RETURN_TYPE = YES + + +GCC_WARN_SIGN_COMPARE = YES + + +GCC_WARN_UNDECLARED_SELECTOR = YES + + +GCC_WARN_UNINITIALIZED_AUTOS = YES + + +GCC_WARN_UNUSED_FUNCTION = YES + + +GCC_WARN_UNUSED_VARIABLE = YES + + +GCC_NO_COMMON_BLOCKS = YES; + +// Precompiled Header Uses Files From Build Directory +// +// This setting allows for better control of sharing precompiled prefix header files +// between projects. By default, Xcode assumes that the prefix header file may include +// header files from the build directory if the build directory is outside of the project +// directory. (Xcode cannot determine this ahead of time since other projects may not +// have been built into the shared build directory at the time the information is +// needed.) +// +// If your prefix file never includes files from the build directory you may set this to +// "NO" to improve sharing of precompiled headers. If the prefix does use files from a +// build directory which is inside your project directory, you may set this to "YES" to +// avoid unintended sharing that may result in build failures. + +PRECOMPS_INCLUDE_HEADERS_FROM_BUILT_PRODUCTS_DIR = NO + + +// Base SDK +// +// The name or path of the base SDK being used during the build. The product will be +// built against the headers and libraries located inside the indicated SDK. This path +// will be prepended to all search paths, and will be passed through the environment to +// the compiler and linker. Additional SDKs can be specified in the ADDITIONAL_SDKS +// setting. + +SDKROOT = iphoneos + + +WARNING_CFLAGS = -Wall -Wextra -Wundef -Wfloat-equal + +PRODUCT_BUNDLE_IDENTIFIER = com.paystack.$(PRODUCT_NAME:rfc1034identifier) diff --git a/CHANGELOG b/CHANGELOG index c66148f..2558431 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,45 @@ +=== 3.0.13 2019-11-4 + +* Updates deprecated class UIWebView to WKWebView + +=== 3.0.6 2019-03-29 + +* Change title and placeholder of OTP dialog + +=== 3.0.5 2018-04-13 + +* Allow adding an NSMutableDictionary or an NSMutableArray directly to metadata + +=== 3.0.4 2017-05-25 + +* Hotfix Ensure we notify caller when showing or dismissing our dialogs + +=== 3.0.3 2017-05-24 + +* Hotfix Ensure we notify caller when showing or dismissing our dialogs + +=== 2.1.1 2016-12-08 + +* Hotfix Add TransactionParams to PaystackIOSStatic's headers + +=== 2.1.1 2016-11-13 + +* Add plan and currency + +Add plan and currency support + +=== 2.1.0 2016-11-13 + +* Add Metadata + +Add Metadata support + +=== 2.0.0 2016-10-22 + +* Charge Cards + +Add Charge Cards functionality + === 1.0.0 2016-03-03 * Initial release diff --git a/Dangerfile.swift b/Dangerfile.swift new file mode 100644 index 0000000..cba6ebf --- /dev/null +++ b/Dangerfile.swift @@ -0,0 +1,33 @@ +// swiftlint:disable all +import Danger + +fileprivate extension Danger.File { + var isInTests: Bool { hasPrefix("PaystackTests/") } + + var isSourceFile: Bool { + hasSuffix(".swift") || hasSuffix(".h") || hasSuffix(".m") + } +} + +let danger = Danger() + +let hasSourceChanges = (danger.git.modifiedFiles + danger.git.createdFiles).contains { $0.isSourceFile } +//SwiftLint +SwiftLint.lint(configFile: ".swiftlint.yml") + +// Encourage smaller PRs +let bigPRThreshold = 50 +if (danger.github.pullRequest.additions! + danger.github.pullRequest.deletions! > bigPRThreshold) { + warn("Pull Request size seems relatively large. If this Pull Request contains multiple changes, please split each into separate PR will helps faster, easier review."); +} + +// Make it more obvious that a PR is a work in progress and shouldn't be merged yet +if danger.github?.pullRequest.title.contains("WIP") == true { + warn("PR is marked as Work in Progress") +} + +// Warn when files has been updated but not tests. +if hasSourceChanges && !danger.git.modifiedFiles.contains(where: { $0.isInTests }) { + warn("The source files were changed, but the tests remain unmodified. Consider updating or adding to the tests to match the source changes.") +} + diff --git a/Example/Default-568h@2x.png b/Example/Default-568h@2x.png deleted file mode 100644 index 205a2cb..0000000 Binary files a/Example/Default-568h@2x.png and /dev/null differ diff --git a/Example/Paystack iOS Example.xcodeproj/project.pbxproj b/Example/Paystack iOS Example.xcodeproj/project.pbxproj index 7d7bda0..833a015 100644 --- a/Example/Paystack iOS Example.xcodeproj/project.pbxproj +++ b/Example/Paystack iOS Example.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 48; objects = { /* Begin PBXBuildFile section */ @@ -11,7 +11,6 @@ 042CA41F1A685E8D00D778E7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 042CA4171A685E8D00D778E7 /* Main.storyboard */; }; 042CA4201A685E8D00D778E7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 042CA4191A685E8D00D778E7 /* Images.xcassets */; }; 042CA4221A685E8D00D778E7 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 042CA41C1A685E8D00D778E7 /* ViewController.swift */; }; - 10A653A71C88CFBC00EBC974 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 10A653A61C88CFBC00EBC974 /* Default-568h@2x.png */; }; 10DAC7941C896BB300855971 /* Paystack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 10DAC7921C896BAC00855971 /* Paystack.framework */; }; 10DAC7951C896BB300855971 /* Paystack.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 10DAC7921C896BAC00855971 /* Paystack.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 10FC52021C88D40A004A0733 /* Paystack iOS Example.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = 10FC52011C88D40A004A0733 /* Paystack iOS Example.entitlements */; }; @@ -49,7 +48,6 @@ 042CA41A1A685E8D00D778E7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 042CA41C1A685E8D00D778E7 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 04823F781A6849200098400B /* Paystack iOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Paystack iOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - 10A653A61C88CFBC00EBC974 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 10DAC7921C896BAC00855971 /* Paystack.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Paystack.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 10FC52011C88D40A004A0733 /* Paystack iOS Example.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "Paystack iOS Example.entitlements"; sourceTree = ""; }; C11745D31C456C730029936F /* Paystack iOS Application Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Paystack iOS Application Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -93,7 +91,6 @@ 04823F6F1A6849200098400B = { isa = PBXGroup; children = ( - 10A653A61C88CFBC00EBC974 /* Default-568h@2x.png */, 042CA4131A685E8D00D778E7 /* Paystack iOS Example */, C11745D41C456C730029936F /* Paystack iOS Application Tests */, 04823F9F1A6849850098400B /* Frameworks */, @@ -174,13 +171,13 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 1030; ORGANIZATIONNAME = Paystack; TargetAttributes = { 04823F771A6849200098400B = { CreatedOnToolsVersion = 6.1.1; DevelopmentTeam = VZCEZ65JNX; - LastSwiftMigration = 0800; + LastSwiftMigration = 1030; SystemCapabilities = { com.apple.InterAppAudio = { enabled = 0; @@ -204,8 +201,8 @@ }; }; buildConfigurationList = 04823F731A6849200098400B /* Build configuration list for PBXProject "Paystack iOS Example" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -230,7 +227,6 @@ 042CA4201A685E8D00D778E7 /* Images.xcassets in Resources */, 042CA41F1A685E8D00D778E7 /* Main.storyboard in Resources */, 10FC52021C88D40A004A0733 /* Paystack iOS Example.entitlements in Resources */, - 10A653A71C88CFBC00EBC974 /* Default-568h@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -287,18 +283,27 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -321,12 +326,13 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = "Paystack iOS Example (Simple)/Paystack iOS Example (Simple)-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; }; name = Debug; }; @@ -334,18 +340,27 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -361,11 +376,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OBJC_BRIDGING_HEADER = "Paystack iOS Example (Simple)/Paystack iOS Example (Simple)-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; VALIDATE_PRODUCT = YES; }; name = Release; @@ -378,6 +394,7 @@ CODE_SIGN_ENTITLEMENTS = "Paystack iOS Example/Paystack iOS Example.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 17; DEVELOPMENT_TEAM = VZCEZ65JNX; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -385,14 +402,15 @@ ); GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Paystack iOS Example/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 3.0.13; PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "Paystack iOS Example"; PROVISIONING_PROFILE = ""; SWIFT_OBJC_BRIDGING_HEADER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -405,6 +423,7 @@ CODE_SIGN_ENTITLEMENTS = "Paystack iOS Example/Paystack iOS Example.entitlements"; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CURRENT_PROJECT_VERSION = 17; DEVELOPMENT_TEAM = VZCEZ65JNX; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -412,13 +431,14 @@ ); GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = "$(SRCROOT)/Paystack iOS Example/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 3.0.13; PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "Paystack iOS Example"; PROVISIONING_PROFILE = ""; SWIFT_OBJC_BRIDGING_HEADER = ""; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -431,7 +451,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "Paystack iOS Application Tests/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.2; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.Paystack-iOS-Application-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -451,7 +471,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "Paystack iOS Application Tests/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 9.2; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.Paystack-iOS-Application-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Example/Paystack iOS Example.xcodeproj/xcshareddata/xcschemes/Paystack iOS Application Tests.xcscheme b/Example/Paystack iOS Example.xcodeproj/xcshareddata/xcschemes/Paystack iOS Application Tests.xcscheme index 8723143..352bed2 100644 --- a/Example/Paystack iOS Example.xcodeproj/xcshareddata/xcschemes/Paystack iOS Application Tests.xcscheme +++ b/Example/Paystack iOS Example.xcodeproj/xcshareddata/xcschemes/Paystack iOS Application Tests.xcscheme @@ -1,6 +1,6 @@ Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { return true } diff --git a/Example/Paystack iOS Example/Base.lproj/Main.storyboard b/Example/Paystack iOS Example/Base.lproj/Main.storyboard index 5887e03..0a2e5c5 100644 --- a/Example/Paystack iOS Example/Base.lproj/Main.storyboard +++ b/Example/Paystack iOS Example/Base.lproj/Main.storyboard @@ -1,9 +1,10 @@ - - + + + - - - + + + @@ -21,96 +22,73 @@ - - - + diff --git a/Example/Paystack iOS Example/Images.xcassets/AppIcon.appiconset/Contents.json b/Example/Paystack iOS Example/Images.xcassets/AppIcon.appiconset/Contents.json index 7e43a3f..8121323 100644 --- a/Example/Paystack iOS Example/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Example/Paystack iOS Example/Images.xcassets/AppIcon.appiconset/Contents.json @@ -2,42 +2,52 @@ "images" : [ { "idiom" : "iphone", - "size" : "20x20", - "scale" : "3x" + "scale" : "2x", + "size" : "20x20" }, { "idiom" : "iphone", - "size" : "29x29", - "scale" : "2x" + "scale" : "3x", + "size" : "20x20" }, { "idiom" : "iphone", - "size" : "29x29", - "scale" : "3x" + "scale" : "2x", + "size" : "29x29" }, { "idiom" : "iphone", - "size" : "40x40", - "scale" : "2x" + "scale" : "3x", + "size" : "29x29" }, { "idiom" : "iphone", - "size" : "40x40", - "scale" : "3x" + "scale" : "2x", + "size" : "40x40" }, { "idiom" : "iphone", - "size" : "60x60", - "scale" : "2x" + "scale" : "3x", + "size" : "40x40" }, { "idiom" : "iphone", - "size" : "60x60", - "scale" : "3x" + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" } ], "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/Example/Paystack iOS Example/Images.xcassets/caps.imageset/Contents.json b/Example/Paystack iOS Example/Images.xcassets/caps.imageset/Contents.json index 5f5eea1..65eab42 100644 --- a/Example/Paystack iOS Example/Images.xcassets/caps.imageset/Contents.json +++ b/Example/Paystack iOS Example/Images.xcassets/caps.imageset/Contents.json @@ -2,12 +2,12 @@ "images" : [ { "idiom" : "iphone", - "filename" : "paystack-cap.jpg", + "filename" : "article-0-1BE8490500000578-267_964x647-1.png", "scale" : "1x" }, { "idiom" : "iphone", - "filename" : "paystack-cap-1.jpg", + "filename" : "article-0-1BE8490500000578-267_964x647.png", "scale" : "2x" }, { diff --git a/Example/Paystack iOS Example/Images.xcassets/caps.imageset/article-0-1BE8490500000578-267_964x647-1.png b/Example/Paystack iOS Example/Images.xcassets/caps.imageset/article-0-1BE8490500000578-267_964x647-1.png new file mode 100644 index 0000000..5a09fda Binary files /dev/null and b/Example/Paystack iOS Example/Images.xcassets/caps.imageset/article-0-1BE8490500000578-267_964x647-1.png differ diff --git a/Example/Paystack iOS Example/Images.xcassets/caps.imageset/article-0-1BE8490500000578-267_964x647.png b/Example/Paystack iOS Example/Images.xcassets/caps.imageset/article-0-1BE8490500000578-267_964x647.png new file mode 100644 index 0000000..5a09fda Binary files /dev/null and b/Example/Paystack iOS Example/Images.xcassets/caps.imageset/article-0-1BE8490500000578-267_964x647.png differ diff --git a/Example/Paystack iOS Example/Images.xcassets/caps.imageset/paystack-cap-1.jpg b/Example/Paystack iOS Example/Images.xcassets/caps.imageset/paystack-cap-1.jpg deleted file mode 100644 index 544d8d8..0000000 Binary files a/Example/Paystack iOS Example/Images.xcassets/caps.imageset/paystack-cap-1.jpg and /dev/null differ diff --git a/Example/Paystack iOS Example/Images.xcassets/caps.imageset/paystack-cap.jpg b/Example/Paystack iOS Example/Images.xcassets/caps.imageset/paystack-cap.jpg deleted file mode 100644 index 544d8d8..0000000 Binary files a/Example/Paystack iOS Example/Images.xcassets/caps.imageset/paystack-cap.jpg and /dev/null differ diff --git a/Example/Paystack iOS Example/Info.plist b/Example/Paystack iOS Example/Info.plist index 6905cc6..64e2267 100644 --- a/Example/Paystack iOS Example/Info.plist +++ b/Example/Paystack iOS Example/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS UILaunchStoryboardName diff --git a/Example/Paystack iOS Example/ViewController.swift b/Example/Paystack iOS Example/ViewController.swift index 9f16aa9..f239570 100644 --- a/Example/Paystack iOS Example/ViewController.swift +++ b/Example/Paystack iOS Example/ViewController.swift @@ -11,23 +11,20 @@ class ViewController: UIViewController, PSTCKPaymentCardTextFieldDelegate { // MARK: REPLACE THESE // Replace these values with your application's keys // Find this at https://dashboard.paystack.co/#/settings/developer - let paystackPublishableKey = "pk_live_2bf31d4aea08ab31f5d0cfd645c7e4f67025d259" + let paystackPublicKey = "" + // let paystackPublicKey = "pk_live_ed8a7004bb85bf636a0a22c635ad9d1788caa5de" - // To set this up, see https://github.com/PaystackHQ/sample-charge-token-backend - let backendChargeURLString = "" - - let capPrice : UInt = 10000 // this is in kobo (so 100 Naira); + // To set this up, see https://github.com/PaystackHQ/sample-charge-card-backend + let backendURLString = "" + // let backendURLString = "https://calm-scrubland-33409.herokuapp.com" let card : PSTCKCard = PSTCKCard() // MARK: Overrides override func viewDidLoad() { // hide token label and email box - tokenLabel.text="" - tokenLabel.isHidden = true - chargeTokenButton.isHidden=true - emailText.isHidden=true - requestTokenButton.isEnabled = false + tokenLabel.text=nil + chargeCardButton.isEnabled = false // clear text from card details // comment these to use the sample data set super.viewDidLoad(); @@ -38,9 +35,9 @@ class ViewController: UIViewController, PSTCKPaymentCardTextFieldDelegate { let alert = UIAlertController( title: title, message: message, - preferredStyle: UIAlertControllerStyle.alert + preferredStyle: UIAlertController.Style.alert ) - let action = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil) + let action = UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil) alert.addAction(action) present(alert, animated: true, completion: nil) } @@ -48,106 +45,156 @@ class ViewController: UIViewController, PSTCKPaymentCardTextFieldDelegate { func dismissKeyboardIfAny(){ // Dismiss Keyboard if any cardDetailsForm.resignFirstResponder() - emailText.resignFirstResponder() } // MARK: Properties - @IBOutlet weak var requestTokenButton: UIButton! - @IBOutlet weak var tokenLabel: UILabel! - @IBOutlet weak var chargeTokenButton: UIButton! - @IBOutlet weak var emailText: UITextField! @IBOutlet weak var cardDetailsForm: PSTCKPaymentCardTextField! - - var tokenString: String? { - return tokenLabel.text - } - - var emailAddress: String? { - return emailText.text - } - + @IBOutlet weak var chargeCardButton: UIButton! + @IBOutlet weak var tokenLabel: UILabel! // MARK: Actions @IBAction func cardDetailsChanged(_ sender: PSTCKPaymentCardTextField) { - requestTokenButton.isEnabled = sender.isValid + chargeCardButton.isEnabled = sender.isValid } - @IBAction func requestToken(_ sender: UIButton) { - dismissKeyboardIfAny() - + + @IBAction func chargeCard(_ sender: UIButton) { -// card.validateCardReturningError() + dismissKeyboardIfAny() // Make sure public key has been set - if (paystackPublishableKey == "" || !paystackPublishableKey.hasPrefix("pk_")) { - showOkayableMessage("You need to set your Paystack publishable key.", message:"You can find your publishable key at https://dashboard.paystack.co/#/settings/developer .") - // You need to set your Paystack publishable key. + if (paystackPublicKey == "" || !paystackPublicKey.hasPrefix("pk_")) { + showOkayableMessage("You need to set your Paystack public key.", message:"You can find your public key at https://dashboard.paystack.co/#/settings/developer .") + // You need to set your Paystack public key. return } - Paystack.setDefaultPublishableKey(paystackPublishableKey) - // use library to create token request and return a token + + Paystack.setDefaultPublicKey(paystackPublicKey) + if cardDetailsForm.isValid { - PSTCKAPIClient.shared().createToken(withCard: cardDetailsForm.cardParams) { (token, error) -> Void in - if let error = error { - print(error.localizedDescription) - } - else if let token = token { - self.tokenLabel.text = token.tokenId - self.tokenLabel.isHidden = false - self.chargeTokenButton.isHidden=false - self.emailText.isHidden=false - } + + if backendURLString != "" { + fetchAccessCodeAndChargeCard() + return } + showOkayableMessage("Backend not configured", message:"To run this sample, please configure your backend.") +// chargeWithSDK(newCode:""); + } } - @IBAction func chargeToken(_: UIButton) { - dismissKeyboardIfAny() - if let _ = tokenString { - if let e = emailAddress{ - if e.isEmail{ - - if backendChargeURLString != "" { - if let url = URL(string: backendChargeURLString + "/charge") { - - let session = URLSession(configuration: URLSessionConfiguration.default) - let request = NSMutableURLRequest(url: url) - request.httpMethod = "POST" - let postBody = "token=\(tokenString!)&amountinkobo=\(capPrice)&email=\(emailAddress!)" - let postData = postBody.data(using: String.Encoding.utf8, allowLossyConversion: false) - session.uploadTask(with: request as URLRequest, from: postData, completionHandler: { data, response, error in - let successfulResponse = (response as? HTTPURLResponse)?.statusCode == 200 - if successfulResponse && error == nil && data != nil{ - // All was well - let newStr = NSString(data: data!, encoding: String.Encoding.utf8.rawValue) - print(newStr) - } else { - if let e=error { - print(e.localizedDescription) - } else { - // There was no error returned though status code was not 200 - print("There was an error communicating with your payment backend.") - } - - } - }).resume() - - return - } + func outputOnLabel(str: String){ + DispatchQueue.main.async { + if let former = self.tokenLabel.text { + self.tokenLabel.text = former + "\n" + str + } else { + self.tokenLabel.text = str + } + } + } + + func fetchAccessCodeAndChargeCard(){ + if let url = URL(string: backendURLString + "/new-access-code") { + self.makeBackendRequest(url: url, message: "fetching access code", completion: { str in + self.outputOnLabel(str: "Fetched access code: "+str) + self.chargeWithSDK(newCode: str as NSString) + }) + } + } + + func chargeWithSDK(newCode: NSString){ + let transactionParams = PSTCKTransactionParams.init(); + transactionParams.access_code = newCode as String; + //transactionParams.additionalAPIParameters = ["enforce_otp": "true"]; +// transactionParams.email = "ibrahim@paystack.co"; +// transactionParams.amount = 2000; +// let dictParams: NSMutableDictionary = [ +// "recurring": true +// ]; +// let arrParams: NSMutableArray = [ +// "0","go" +// ]; +// do { +// try transactionParams.setMetadataValueDict(dictParams, forKey: "custom_filters"); +// try transactionParams.setMetadataValueArray(arrParams, forKey: "custom_array"); +// } catch { +// print(error) +// } + // use library to create charge and get its reference + PSTCKAPIClient.shared().chargeCard(self.cardDetailsForm.cardParams, forTransaction: transactionParams, on: self, didEndWithError: { (error, reference) in + self.outputOnLabel(str: "Charge errored") + // what should I do if an error occured? + print(error) + if error._code == PSTCKErrorCode.PSTCKExpiredAccessCodeError.rawValue{ + // access code could not be used + // we may as well try afresh + } + if error._code == PSTCKErrorCode.PSTCKConflictError.rawValue{ + // another transaction is currently being + // processed by the SDK... please wait + } + if let errorDict = (error._userInfo as! NSDictionary?){ + if let errorString = errorDict.value(forKeyPath: "com.paystack.lib:ErrorMessageKey") as! String? { + if let reference=reference { + self.showOkayableMessage("An error occured while completing "+reference, message: errorString) + self.outputOnLabel(str: reference + ": " + errorString) + self.verifyTransaction(reference: reference) + } else { + self.showOkayableMessage("An error occured", message: errorString) + self.outputOnLabel(str: errorString) } - showOkayableMessage("Backend not configured", message:"You created a token! Its value is \(tokenString!). Now configure your backend to accept this token and complete a charge.") - return } } - showOkayableMessage("Email not provided", message:"You should enter a valid email!") - return - + self.chargeCardButton.isEnabled = true; + }, didRequestValidation: { (reference) in + self.outputOnLabel(str: "requested validation: " + reference) + }, willPresentDialog: { + // make sure dialog can show + // if using a "processing" dialog, please hide it + self.outputOnLabel(str: "will show a dialog") + }, dismissedDialog: { + // if using a processing dialog, please make it visible again + self.outputOnLabel(str: "dismissed dialog") + }) { (reference) in + self.outputOnLabel(str: "succeeded: " + reference) + self.chargeCardButton.isEnabled = true; + self.verifyTransaction(reference: reference) } - showOkayableMessage("Token not obtained", message:"You need to create a token before calling charge.") + return } + func verifyTransaction(reference: String){ + if let url = URL(string: backendURLString + "/verify/" + reference) { + makeBackendRequest(url: url, message: "verifying " + reference, completion:{(str) -> Void in + self.outputOnLabel(str: "Message from paystack on verifying " + reference + ": " + str) + }) + } + } + func makeBackendRequest(url: URL, message: String, completion: @escaping (_ result: String) -> Void){ + let session = URLSession(configuration: URLSessionConfiguration.default) + self.outputOnLabel(str: "Backend: " + message) + session.dataTask(with: url, completionHandler: { data, response, error in + let successfulResponse = (response as? HTTPURLResponse)?.statusCode == 200 + if successfulResponse && error == nil && data != nil { + if let str = NSString(data: data!, encoding: String.Encoding.utf8.rawValue){ + completion(str as String) + } else { + self.outputOnLabel(str: " while "+message) + print("") + } + } else { + if let error = error { + print(error.localizedDescription) + self.outputOnLabel(str: error.localizedDescription) + } else { + // There was no error returned though status code was not 200 + print("There was an error communicating with your payment backend.") + self.outputOnLabel(str: "There was an error communicating with your payment backend while "+message) + } + } + }).resume() + } } - diff --git a/GUIDE.md b/GUIDE.md index 788266f..7ae89f3 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -1,33 +1,37 @@ # Guide -If you want to build a mobile app like [Afro](http://www.getafrocab.com) and enable people to make purchases directly in your app, our iOS and [Android](https://github.com/PaystackHQ/paystack-android) libraries can help. +If you want to build mobile apps like [Taxify](http://www.taxify.eu), [Afro](http://www.getafrocab.com), [Okada Books](https://www.okadabooks.com) and enable people to make purchases directly in your app, our iOS and [Android](https://github.com/PaystackHQ/paystack-android) libraries can help. -Accepting payments in your app involves 4 steps, which we'll cover in this guide: +Accepting payments in your app after collecting card information can be achieved by charging the card with our SDK. Reusable `authorization code`s from such transaction from can be used from your backend to charge the cards directly. -- Collecting credit card information from your customer -- Converting the credit card information to a _**single-use**_ token -- Sending this token to your server to create a charge which provides an authorizaton code if successful -- Using the returned authorization code to make future charge requests +## Summarized flow -## Getting Started +Once it's time to pay, and the user has provided card details on your app, -### Step 1: Install the library +#### OPTION 1: Backend starts transaction (recommended) -#### Using [CocoaPods](https://cocoapods.org/) +a. App prompts backend to initialize a transaction, backend returns `access_code`. -We recommend using [CocoaPods](https://cocoapods.org/) to install the Paystack iOS library, since it makes it easy to keep your app's dependencies up to date. +b. Provide `access_code` and card details to our SDK's `chargeCard` function. -If you haven't set up Cocoapods before, their site has installation instructions. Then, add pod 'Paystack' to your Podfile, and run pod install. +#### OPTION 2: App starts transaction -(Don't forget to use the .xcworkspace file to open your project in Xcode, instead of the .xcodeproj file, from here on out.) +a. Provide transaction parameters and card params to our SDK's `chargeCard` function. -#### Using Carthage +#### SDK will prompt user for PIN, OTP or Bank authentication as required + +#### Once successful, we will send event to your webhook url and call the didTransactionSuccess callback -We also support installing our SDK using Carthage. You can simply add github "paystackhq/paystack-ios" to your Cartfile, and follow the Carthage installation instructions. + +## Getting Started + +### Step 0: Add Keychain Sharing entitlements to your app + +### Step 1: Install the library #### Manual installation -We also publish our SDK as a static framework that you can copy directly into your app without any additional tools: +We publish our SDK as a static framework that you can copy directly into your app without any additional tools: - Head to our [releases page](https://github.com/PaystackHQ/paystack-ios/releases/) and download the framework that's right for you. - Unzip the file you downloaded. @@ -37,9 +41,21 @@ We also publish our SDK as a static framework that you can copy directly into yo - Click 'Add'. - In your project settings, go to the "Build Settings" tab, and make sure -ObjC is present under "Other Linker Flags". +#### Using [CocoaPods](https://cocoapods.org/) + +We recommend using [CocoaPods](https://cocoapods.org/) to install the Paystack iOS library, since it makes it easy to keep your app's dependencies up to date. + +If you haven't set up Cocoapods before, their site has installation instructions. Then, add pod 'Paystack' to your Podfile, and run pod install. + +(Don't forget to use the .xcworkspace file to open your project in Xcode, instead of the .xcodeproj file, from here on out.) + +#### Using Carthage + +We also support installing our SDK using Carthage. You can simply add github "paystackhq/paystack-ios" to your Cartfile, and follow the Carthage installation instructions. + ### Step 2: Configure API keys -First, you'll want to configure Paystack with your publishable API key. We recommend doing this in your `AppDelegate`'s `application:didFinishLaunchingWithOptions:` method so that it'll be set for the entire lifecycle of your app. +First, you'll want to configure Paystack with your public API key. We recommend doing this in your `AppDelegate`'s `application:didFinishLaunchingWithOptions:` method so that it will be set for the entire lifecycle of your app. ```Swift // AppDelegate.swift @@ -49,7 +65,7 @@ import Paystack @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - Paystack.setDefaultPublishableKey("pk_test_xxxx") + Paystack.setDefaultPublicKey("pk_test_xxxx") return true } } @@ -65,22 +81,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate { - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [Paystack setDefaultPublishableKey:@"pk_test_xxxxx"]; + [Paystack setDefaultPublicKey:@"pk_test_xxxxx"]; return YES; } @end ``` -We've placed a test publishable API key as the PaystackPublishableKey constant in the above snippet. You'll need to swap it out with your live publishable key in production. You can see all your API keys in your dashboard. +We've placed a test public API key as the PaystackPublicKey constant in the above snippet. You'll need to swap it out with your live public key in production. You can see all your API keys in your dashboard. ### Step 3: Collecting credit card information #### Test Mode -When you're using your test publishable key, our libraries give you the ability to test your payment flow without having to charge real credit cards. +When you're using your test public key, our libraries give you the ability to test your payment flow without having to charge real credit cards. + +If you're building your own form or using `PSTCKPaymentCardTextField`, using any of: -If you're building your own form or using `PSTCKPaymentCardTextField`, using the card number `4123450131001381` with CVC `883` (along with any future expiration date) will accomplish the same effect. +1. card number `4084084084084081` with CVC `408` (along with any future expiration date); or; +2. card number `5060666666666666666` with CVC `123` and any future expiration date, PIN `1234`, OTP `123456` + +will accomplish the same effect. At some point in the flow of your app, you'll want to obtain payment details from the user. There are two ways to do this. You can (in increasing order of complexity): @@ -155,62 +176,166 @@ func paymentCardTextFieldDidChange(textField: PSTCKPaymentCardTextField) { If you build your own payment form, you'll need to collect at least your customers' card numbers, CVC and expiration dates. -### Step 4: Creating Tokens +### Step 4: Assembling Card information into `PSTCKCardParams` -Our libraries shoulder the burden of PCI compliance by helping you avoid the need to send card data directly to your server. Instead, our libraries send credit card data directly to our servers, where we can convert them to tokens. You should charge these tokens later in your server-side code to get an authorization code. +If you're using `PSTCKPaymentCardTextField`, simply call its `cardParams` property to get the assembled card data. -#### Using `PSTCKCardParams` +```Swift +let cardParams = paymentTextField.cardParams as PSTCKCardParams +``` -If you're using `PSTCKPaymentCardTextField` or your own form, you can assemble the data into an `PSTCKCardParams` object. Once you've collected the card number, expiration, and CVC, package them up in an `PSTCKCardParams` object and invoke the `createTokenWithCard:` method on the `PSTCKAPIClient` class, instructing the library to send off the credit card data to Paystack and return a token. +```Objective-C +PSTCKCardParams cardParams = [paymentTextField cardParams]; +``` + +If you are using your own form, you can assemble the data into an `PSTCKCardParams` object thus: ```Swift -@IBAction func save(sender: UIButton) { - if let card = paymentTextField.cardParams as? PSTCKCardParams { - PSTCKAPIClient.sharedClient().createTokenWithCard(card) { (token, error) -> Void in - if let error = error { - handleError(error) - } - else if let token = token { - ... - } - } - } -} +let cardParams = PSTCKCardParams.init(); + +// then set parameters thus from card +cardParams.number = card.number +cardParams.cvc = card.cvc +cardParams.expYear = card.expYear +cardParams.expMonth = card.expMonth + +// or directly +cardParams.number = "2963781976222" +cardParams.cvc = "289" +cardParams.expYear = 2018 +cardParams.expMonth = 9 ``` ```Objective-C -- (IBAction)save:(UIButton *)sender { - [[PSTCKAPIClient sharedClient] - createTokenWithCard:self.paymentTextField.cardParams - completion:^(PSTCKToken *token, NSError *error) { - if (error) { - [self handleError:error]; - } else { - // call your createBackendChargeWithToken function - // A sample is presented in step 5 - } - }]; +PSTCKCardParams cardParams = [[PSTCKCardParams alloc] init]; + +// then set parameters thus from card +cardParams.number = [card number]; +cardParams.cvc = [card cvc]; +cardParams.expYear = [card expYear]; +cardParams.expMonth = [card expMonth]; + +// or directly +cardParams.number = "2963781976222"; +cardParams.cvc = "289"; +cardParams.expYear = 2018; +cardParams.expMonth = 9; +``` + +### Step 4: Getting payments + +Our libraries shoulder the burden of PCI compliance by helping you avoid the need to send card data directly to your server. Instead, our libraries send credit card data directly to our servers, where we can charge them or create authorizations which you charge on your server. + +We charge cards you send using parameters provided in your `PSTCKTransactionParams`. Assemble Transaction parameters into `PSTCKTransactionParams`, and send them along with the `cardParams` from the previous step to get a charge. + +- **CardParams** - As gathered in [Step 3](#step-4-assembling-card-information-into-pstckcardparams) + +- **TransactionParams** - This object allows you provide information about the transaction to be made. This can be used in either of 2 ways: + - **Resume an initialized transaction**: If employing this flow, you would send all required parameters + for the transaction from your backend to the Paystack API via the `transaction/initialize` call - + documented [here](https://developers.paystack.co/reference#initialize-a-transaction).. The + response of the call includes an `access_code`. This can be used to charge the card by doing + `transactionParams.access_code = {value from backend});`. Once an access code is set, others will be ignored. + - **Initiate a fresh transaction on Paystack**: By setting the parameters: `amount`, `email`, `currency`, `plan`, + `subaccount`, `transactionCharge`, `reference`, `bearer`. And calling the `setCustomFieldValue` and `setMetadataValue` + you can set up a fresh transaction directly from the SDK. + Documentation for these parameters are same as for `transaction/initialize`. + +- **ViewController** - A view controller to be used when presenting dialogs. The currently open ViewController is perfect. + +You will need to specify callbacks too. Each will be called depending on how the transaction went. + +- **didTransactionSuccess** will be called once the charge succeeds. + +- **didRequestValidation** is called every time the SDK needs to request user input. This function currently only allows the app know that the SDK is requesting further user input. + +- **didEndWithError** is called if an error occurred during processing. Some types that you should watch include + - *PSTCKErrorCode.PSTCKExpiredAccessCodeError*: This would be thrown if the access code has already been used to attempt a charge. + - *PSTCKErrorCode.PSTCKConflictError*: This would be thrown if another transaction is currently being processed by the SDK + + +```Swift +@IBAction func charge(sender: UIButton) { + // cardParams already fetched from our view or assembled by you + let transactionParams = PSTCKTransactionParams.init(); + + // building new Paystack Transaction + transactionParams.amount = 1390; + let custom_filters: NSMutableDictionary = [ + "recurring": true + ]; + let items: NSMutableArray = [ + "Bag","Glasses" + ]; + do { + try transactionParams.setCustomFieldValue("iOS SDK", displayedAs: "Paid Via"); + try transactionParams.setCustomFieldValue("Paystack hats", displayedAs: "To Buy"); + try transactionParams.setMetadataValue("iOS SDK", forKey: "paid_via"); + try transactionParams.setMetadataValueDict(custom_filters, forKey: "custom_filters"); + try transactionParams.setMetadataValueArray(items, forKey: "items"); + } catch { + print(error); + } + transactionParams.email = "e@ma.il"; + + // check https://developers.paystack.co/docs/split-payments-overview for details on how these work + // transactionParams.subaccount = "ACCT_80d907euhish8d"; + // transactionParams.bearer = "subaccount"; + // transactionParams.transaction_charge = 280; + + // if a reference is not supplied, we will give one + // transactionParams.reference = "ChargedFromiOSSDK@" + + PSTCKAPIClient.shared().chargeCard(cardParams, forTransaction: transactionParams, on: viewController, + didEndWithError: { (error, reference) -> Void in + handleError(error) + }, didRequestValidation: { (reference) -> Void in + // an OTP was requested, transaction has not yet succeeded + }, didTransactionSuccess: { (reference) -> Void in + // transaction may have succeeded, please verify on backend + }) } ``` -In the example above, we're calling `createTokenWithCard:` when a save button is tapped. The important thing to ensure is the createToken isn't called before the user has finished entering their card details. +```Objective-C +- (IBAction)charge:(UIButton *)sender { + // cardParams already fetched from our view or assembled by you + + PSTCKTransactionParams transactionParams = [[PSTCKTransactionParams alloc] init]; + + // resuming a transaction initialized by backend + transactionParams.access_code = '{access code from server}'; + + [[PSTCKAPIClient sharedClient] chargeCard:cardParams + forTransaction:transactionParams + onViewController: viewController, + didEndWithError:^(NSError *error, NSString *reference){ + [self handleError:error]; + } + didRequestValidation: ^(NSString *reference){ + // an OTP was requested, transaction has not yet succeeded + } + didTransactionSuccess: ^(NSString *reference){ + // transaction may have succeeded, please verify on backend + }]; -Handling error messages and showing activity indicators while we're creating the token is up to you. +} +``` -### Step 5: Sending the token to your server +### Step 5: Send the reference to your backend -The block you gave to `createToken` will be called whenever Paystack returns with a token (or error). You'll need to send the token off to your server so you can, for example, charge the card. +The blocks you gave to `chargeCard` will be called whenever Paystack returns with a reference (or error). You'll need to send the `reference` off to your backend so you can verify the transactions. Here's how it looks: ```Swift // ViewController.swift -func createBackendChargeWithToken(token: PSTCKToken, amountinkobo: Int, emailAddress: String) { - let url = NSURL(string: "https://example.com/token")! +func verifyCharge(reference: String) { + let url = NSURL(string: "https://example.com/verify")! let request = NSMutableURLRequest(URL: url) request.HTTPMethod = "POST" - let postBody = "token=\(token.tokenId!)&amountinkobo=\(amountinkobo)&email=\(emailAddress!)" + let postBody = "reference=reference" let postData = postBody.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) session.uploadTaskWithRequest(request, fromData: postData, completionHandler: { data, response, error in let successfulResponse = (response as? NSHTTPURLResponse)?.statusCode == 200 @@ -226,7 +351,7 @@ func createBackendChargeWithToken(token: PSTCKToken, amountinkobo: Int, emailAdd print("There was an error communicating with your payment backend.") // All we did here is log it to the output window } - + } }).resume() } @@ -235,14 +360,12 @@ func createBackendChargeWithToken(token: PSTCKToken, amountinkobo: Int, emailAdd ```Objective-C // ViewController.m -- (void)createBackendChargeWithToken:(PSTCKToken *)token, - (NSInt *) amountinkobo, - (NSString *) emailAddress +- (void)verifyCharge:(String *)reference { - NSURL *url = [NSURL URLWithString:@"https://example.com/token"]; + NSURL *url = [NSURL URLWithString:@"https://example.com/verify"]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; - NSString *body = [NSString stringWithFormat:@"token=%@&amountinkobo=%@&email=%@", token.tokenId, amountinkobo, emailAddress]; + NSString *body = [NSString stringWithFormat:@"reference=%@", reference]; request.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding]; NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration]; @@ -262,79 +385,29 @@ func createBackendChargeWithToken(token: PSTCKToken, amountinkobo: Int, emailAdd ``` -On the server, you just need to implement an endpoint that will accept the parameters `token`, `email` and `amountinkobo`. Make sure any communication with your server is SSL secured to prevent eavesdropping. +On the server, you just need to implement an endpoint that will accept the parameter: `reference`. Make sure any communication with your backend is SSL secured to prevent eavesdropping. + --------------------- +### Step 6: Implement verification on your server +Verify a charge by calling our REST API. An active `authorization_code` will be returned once the card has been charged successfully. You can learn more about our API [here](https://developers.paystack.co/docs/getting-started). -### Step 6: Implement payment on your server -Create a charge by calling our REST API. An `authorization_code` will be returned once the _single-use_ token has been charged successfully. You can learn more about our API [here](https://developers.paystack.co/docs/getting-started). - - **Endpoint:** https://api.paystack.co/transaction/charge_token + **Endpoint:** GET: https://api.paystack.co/transaction/verify + + **Documentation:** https://developers.paystack.co/docs/verify-transaction **Parameters:** - - - token - the token you want to charge - - email - customer's email address (required) - - reference - unique reference (required) - - amount - Amount in Kobo (required) + + - reference - the transaction reference **Example** ```bash - $ curl https://api.paystack.co/transaction/charge_token \ + $ curl https://api.paystack.co/transaction/verify/trx_sjdhf2987hb \ -H "Authorization: Bearer SECRET_KEY" \ -H "Content-Type: application/json" \ - -d '{"token": "PSTK_r4ec2m75mrgsd8n9", "email": "customer@email.com", "amount": 10000, "reference": "amutaJHSYGWakinlade256"}' \ - -X POST - -``` -### Using the [Paystack-PHP library](https://github.com/yabacon/paystack-php) or [Paystack PHP class](https://github.com/yabacon/paystack-class) -```php -list($headers, $body, $code) = $paystack->transaction->chargeToken([ - 'reference'=>'amutaJHSYGWakinlade256', - 'token'=>'PSTK_r4ec2m75mrgsd8n9', - 'email'=>'customer@email.com', - 'amount'=>10000 // in kobo - ]); - -// check if authorization code was generated -if ((intval($code) === 200) && array_key_exists('status', $body) && $body['status']) { - // body contains Array with data similar to result below - $authorization_code = $body['authorization']['authorization_code']; - // save the authorization_code so you may charge in future -} else { - // invalid body was returned - // handle this or troubleshoot - throw new \Exception('Transaction Initialise returned non-true status'); -} - -``` + -X GET -**Result** -```json - { - "status": true, - "message": "Charge successful", - "data": { - "amount": 10000, - "transaction_date": "2016-01-26T15:34:02.000Z", - "status": "success", - "reference": "amutaJHSYGWakinlade256", - "domain": "test", - "authorization": { - "authorization_code": "AUTH_d47nbp3x", - "card_type": "visa", - "last4": "1111", - "bank": null - }, - "customer": { - "first_name": "John", - "last_name": "Doe", - "email": "customer@email.com" - }, - "plan": 0 - } ``` ### Charging Returning Customers -See details for charging returning customers [here](https://developers.paystack.co/docs/charging-returning-customers). +See details for charging returning customers [here](https://developers.paystack.co/docs/charging-returning-customers). Note that only `reusable` authorizations can be charged with this endpoint. diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..484334d --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem "fastlane" +gem "slather" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..2969c4b --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,248 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.6) + rexml + activesupport (7.1.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.5) + public_suffix (>= 2.0.2, < 6.0) + artifactory (3.0.15) + atomos (0.1.3) + aws-eventstream (1.2.0) + aws-partitions (1.850.0) + aws-sdk-core (3.186.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.5) + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.72.0) + aws-sdk-core (~> 3, >= 3.184.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.136.0) + aws-sdk-core (~> 3, >= 3.181.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.6) + aws-sigv4 (1.6.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.2.0) + bigdecimal (3.1.4) + claide (1.1.0) + clamp (1.3.2) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) + declarative (0.0.20) + digest-crc (0.6.5) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20231109) + dotenv (2.8.1) + drb (2.2.0) + ruby2_keywords + emoji_regex (3.2.3) + excon (0.104.0) + faraday (1.10.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.7) + faraday (>= 0.8.0) + http-cookie (~> 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.4) + multipart-post (~> 2) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.0) + faraday (~> 1.0) + fastimage (2.2.7) + fastlane (2.217.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.0) + babosa (>= 1.0.3, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander (~> 4.6) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + naturally (~> 2.2) + optparse (~> 0.1.1) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + gh_inspector (1.1.3) + google-apis-androidpublisher_v3 (0.52.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.2) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) + mini_mime (~> 1.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + rexml + webrick + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.29.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.6.0) + google-cloud-env (~> 1.0) + google-cloud-errors (~> 1.0) + google-cloud-env (1.6.0) + faraday (>= 0.17.3, < 3.0) + google-cloud-errors (1.3.1) + google-cloud-storage (1.45.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-iamcredentials_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.29.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) + mini_mime (~> 1.0) + googleauth (1.8.1) + faraday (>= 0.17.3, < 3.a) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.5) + domain_name (~> 0.5) + httpclient (2.8.3) + i18n (1.14.1) + concurrent-ruby (~> 1.0) + jmespath (1.6.2) + json (2.6.3) + jwt (2.7.1) + mini_magick (4.12.0) + mini_mime (1.1.5) + mini_portile2 (2.8.5) + minitest (5.20.0) + multi_json (1.15.0) + multipart-post (2.3.0) + mutex_m (0.2.0) + nanaimo (0.3.0) + naturally (2.2.1) + nokogiri (1.15.4) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + optparse (0.1.1) + os (1.1.4) + plist (3.7.0) + public_suffix (5.0.3) + racc (1.7.3) + rake (13.1.0) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rexml (3.2.6) + rouge (2.0.7) + ruby2_keywords (0.0.5) + rubyzip (2.3.2) + security (0.1.3) + signet (0.18.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + slather (2.8.0) + CFPropertyList (>= 2.2, < 4) + activesupport + clamp (~> 1.3) + nokogiri (>= 1.14.3) + xcodeproj (~> 1.21) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.1) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + uber (0.1.0) + unicode-display_width (2.5.0) + webrick (1.8.1) + word_wrap (1.0.0) + xcodeproj (1.23.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + fastlane + slather + +BUNDLED WITH + 2.4.10 diff --git a/LICENSE b/LICENSE index be7f503..b1abb3b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2016- Paystack, Inc. (https://paystack.com) +Copyright (c) 2017 Paystack, Inc. (https://paystack.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Paystack.podspec b/Paystack.podspec index 2b54f56..9d4a6d0 100644 --- a/Paystack.podspec +++ b/Paystack.podspec @@ -1,30 +1,28 @@ Pod::Spec.new do |s| s.name = 'Paystack' - s.version = '1.0.1' + s.version = '3.0.17' s.summary = 'Paystack is a web-based API helping African Businesses accept payments online.' s.description = <<-DESC Paystack makes it easy for African Businesses to accept Mastercard, Visa and Verve cards from anyone, anywhere in the world. - - This is the Paystack SDK for iOS. Collect Card details on iOS and get a token. Shoulders the burden of PCI compliance by helping you avoid the need to send card data directly to your server. Instead you send to Paystack's server and get a token which you can charge later in your server-side code. DESC s.license = { :type => 'MIT', :file => 'LICENSE' } s.homepage = 'https://paystack.com' - s.authors = { 'Ibrahim Lawal' => 'ibrahim@paystack.com', 'Paystack' => 'support@paystack.com' } + s.authors = { 'Jubril Olambiwonnu' => 'jubril@paystack.com', 'Ibrahim Lawal' => 'ibrahim@paystack.com', 'Paystack' => 'support@paystack.com' } s.source = { :git => 'https://github.com/paystackhq/paystack-ios.git', :tag => "v#{s.version}" } s.ios.frameworks = 'Foundation', 'Security' s.ios.weak_frameworks = 'PassKit', 'AddressBook' - s.osx.frameworks = 'Foundation', 'Security', 'WebKit' s.requires_arc = true - s.ios.deployment_target = '7.0' - s.osx.deployment_target = '10.9' s.default_subspecs = 'Core' + s.ios.deployment_target = '11.0' + s.swift_versions = '5.0' + s.subspec 'Core' do |ss| - ss.public_header_files = 'Paystack/PublicHeaders/*.h' + ss.public_header_files = 'Paystack/PublicHeaders/*.h', 'Paystack/RSA/*.h' ss.ios.public_header_files = 'Paystack/PublicHeaders/UI/*.h' ss.source_files = 'Paystack/PublicHeaders/*.h', 'Paystack/RSA/*.{h,m}', 'Paystack/*.{h,m}' - ss.ios.source_files = 'Paystack/PublicHeaders/UI/*.h', 'Paystack/UI/*.{h,m}', 'Paystack/Fabric/*' + ss.ios.source_files = 'Paystack/PublicHeaders/UI/*.h', 'Paystack/UI/*.{h,m}', 'Paystack/**/*.{swift}' ss.resources = 'Paystack/Resources/**/*' end diff --git a/Paystack.xcodeproj/project.pbxproj b/Paystack.xcodeproj/project.pbxproj index 89747c9..14ee8fc 100644 --- a/Paystack.xcodeproj/project.pbxproj +++ b/Paystack.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 47; objects = { /* Begin PBXAggregateTarget section */ @@ -115,18 +115,13 @@ 04415C651A6605B5001225ED /* PSTCKToken.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB4CD1A5F30A700B854EE /* PSTCKToken.m */; }; 04415C661A6605B5001225ED /* PaystackError.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB4CF1A5F30A700B854EE /* PaystackError.m */; }; 04415C671A6605B5001225ED /* PSTCKAPIClientTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB51E1A5F3A9300B854EE /* PSTCKAPIClientTest.m */; }; - 04415C681A6605B5001225ED /* PSTCKFormEncoderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB51F1A5F3A9300B854EE /* PSTCKFormEncoderTest.m */; }; - 04415C6D1A6605B5001225ED /* PSTCKCardFunctionalTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB5241A5F3A9300B854EE /* PSTCKCardFunctionalTest.m */; }; - 04415C6E1A6605B5001225ED /* PSTCKCardTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB5251A5F3A9300B854EE /* PSTCKCardTest.m */; }; - 04415C6F1A6605B5001225ED /* PSTCKCertTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB5261A5F3A9300B854EE /* PSTCKCertTest.m */; }; 04415C701A6605B5001225ED /* PSTCKTokenTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB5271A5F3A9300B854EE /* PSTCKTokenTest.m */; }; - 04415C721A6605D9001225ED /* Paystack.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4A91A5F30A700B854EE /* Paystack.h */; }; + 04415C721A6605D9001225ED /* Paystack.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4A91A5F30A700B854EE /* Paystack.h */; settings = {ATTRIBUTES = (Public, ); }; }; 04415C7F1A6605D9001225ED /* PSTCKAPIClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4C21A5F30A700B854EE /* PSTCKAPIClient.h */; }; 04415C801A6605D9001225ED /* PSTCKFormEncoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4C41A5F30A700B854EE /* PSTCKFormEncoder.h */; }; 04415C831A6605D9001225ED /* PSTCKCard.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4CA1A5F30A700B854EE /* PSTCKCard.h */; settings = {ATTRIBUTES = (Public, ); }; }; 04415C841A6605D9001225ED /* PSTCKToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4CC1A5F30A700B854EE /* PSTCKToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; 04415C851A6605D9001225ED /* PaystackError.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4CE1A5F30A700B854EE /* PaystackError.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 04533E7D1A6877F400C7E52E /* PassKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04D5BF9019BF958F009521A5 /* PassKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 045A62AB1B8E7259000165CE /* PSTCKPaymentCardTextFieldTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 045A62AA1B8E7259000165CE /* PSTCKPaymentCardTextFieldTest.m */; }; 045A62AC1B8E73AB000165CE /* pstck_card_amex.png in Resources */ = {isa = PBXBuildFile; fileRef = 0438EF891B741C2800D506CC /* pstck_card_amex.png */; }; 045A62AD1B8E73AB000165CE /* pstck_card_amex@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0438EF8A1B741C2800D506CC /* pstck_card_amex@2x.png */; }; @@ -175,7 +170,6 @@ 049E84D31A605E6A000B66CD /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FAFC12C516E5767F0066297F /* UIKit.framework */; }; 049E84D41A605E7C000B66CD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 11C74B9B164043050071C2CA /* Foundation.framework */; }; 049E84D51A605E82000B66CD /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A0D74F918F6106100966D7B /* Security.framework */; }; - 049E84D61A605E8F000B66CD /* PassKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04D5BF9019BF958F009521A5 /* PassKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 049E84D71A605E99000B66CD /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04B94BC71A47B78A00092C46 /* AddressBook.framework */; }; 049E84D91A605EF0000B66CD /* Paystack.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4A91A5F30A700B854EE /* Paystack.h */; settings = {ATTRIBUTES = (Public, ); }; }; 049E84E61A605EF0000B66CD /* PSTCKAPIClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4C21A5F30A700B854EE /* PSTCKAPIClient.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -214,10 +208,6 @@ 04D12C3E1A5F55D10010446E /* PSTCKToken.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4CC1A5F30A700B854EE /* PSTCKToken.h */; settings = {ATTRIBUTES = (Public, ); }; }; 04D12C3F1A5F55D10010446E /* PaystackError.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDB4CE1A5F30A700B854EE /* PaystackError.h */; settings = {ATTRIBUTES = (Public, ); }; }; 04D12C401A5F55FA0010446E /* PSTCKAPIClientTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB51E1A5F3A9300B854EE /* PSTCKAPIClientTest.m */; }; - 04D12C411A5F55FA0010446E /* PSTCKFormEncoderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB51F1A5F3A9300B854EE /* PSTCKFormEncoderTest.m */; }; - 04D12C451A5F55FA0010446E /* PSTCKCardFunctionalTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB5241A5F3A9300B854EE /* PSTCKCardFunctionalTest.m */; }; - 04D12C461A5F55FA0010446E /* PSTCKCardTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB5251A5F3A9300B854EE /* PSTCKCardTest.m */; }; - 04D12C471A5F55FA0010446E /* PSTCKCertTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB5261A5F3A9300B854EE /* PSTCKCertTest.m */; }; 04D12C481A5F55FA0010446E /* PSTCKTokenTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB5271A5F3A9300B854EE /* PSTCKTokenTest.m */; }; 04E32A9B1B7A93FC009C9E35 /* PSTCKCardValidator.m in Sources */ = {isa = PBXBuildFile; fileRef = 0438EF3F1B74170D00D506CC /* PSTCKCardValidator.m */; }; 04E32A9D1B7A9490009C9E35 /* PSTCKPaymentCardTextField.h in Headers */ = {isa = PBXBuildFile; fileRef = 04E32A9C1B7A9490009C9E35 /* PSTCKPaymentCardTextField.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -239,15 +229,58 @@ 04F213371BCECB1C001D6F22 /* PSTCKAPIResponseDecodable.h in Headers */ = {isa = PBXBuildFile; fileRef = 04F213341BCECB1C001D6F22 /* PSTCKAPIResponseDecodable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 04FCFA191BD59A8C00297732 /* PSTCKCategoryLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 04FCFA171BD59A8C00297732 /* PSTCKCategoryLoader.h */; }; 04FCFA1A1BD59A8C00297732 /* PSTCKCategoryLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FCFA181BD59A8C00297732 /* PSTCKCategoryLoader.m */; }; + 0806F2F41DBB9E7100C2741B /* PSTCKTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 0806F2F21DBB9E7100C2741B /* PSTCKTransaction.h */; }; + 0806F2F51DBB9E7100C2741B /* PSTCKTransactionParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 0806F2F31DBB9E7100C2741B /* PSTCKTransactionParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0806F2F81DBB9E8200C2741B /* PSTCKTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 0806F2F61DBB9E8200C2741B /* PSTCKTransaction.m */; }; + 0806F2F91DBB9E8200C2741B /* PSTCKTransactionParams.m in Sources */ = {isa = PBXBuildFile; fileRef = 0806F2F71DBB9E8200C2741B /* PSTCKTransactionParams.m */; }; + 0806F2FB1DBBA3C500C2741B /* PSTCKValidationParams.m in Sources */ = {isa = PBXBuildFile; fileRef = 0806F2FA1DBBA3C500C2741B /* PSTCKValidationParams.m */; }; + 0806F2FD1DBBA3F600C2741B /* PSTCKValidationParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 0806F2FC1DBBA3F600C2741B /* PSTCKValidationParams.h */; }; + 0806F3011DBBB5DF00C2741B /* pstck_card_verve.png in Resources */ = {isa = PBXBuildFile; fileRef = 0806F2FE1DBBB5DF00C2741B /* pstck_card_verve.png */; }; + 0806F3021DBBB5DF00C2741B /* pstck_card_verve@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0806F2FF1DBBB5DF00C2741B /* pstck_card_verve@2x.png */; }; + 0806F3031DBBB5DF00C2741B /* pstck_card_verve@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0806F3001DBBB5DF00C2741B /* pstck_card_verve@3x.png */; }; + 08A5723C1DF92EC10045E184 /* PSTCKTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 0806F2F61DBB9E8200C2741B /* PSTCKTransaction.m */; }; + 08A5723D1DF92EC10045E184 /* PSTCKTransactionParams.m in Sources */ = {isa = PBXBuildFile; fileRef = 0806F2F71DBB9E8200C2741B /* PSTCKTransactionParams.m */; }; + 08A5723E1DF92EC10045E184 /* PSTCKValidationParams.m in Sources */ = {isa = PBXBuildFile; fileRef = 0806F2FA1DBBA3C500C2741B /* PSTCKValidationParams.m */; }; + 08A5723F1DF92EF30045E184 /* PSTCKCategoryLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FCFA181BD59A8C00297732 /* PSTCKCategoryLoader.m */; }; + 08A572401DF92F1D0045E184 /* PSTCKCategoryLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 04FCFA171BD59A8C00297732 /* PSTCKCategoryLoader.h */; }; + 08A572411DF92F3F0045E184 /* PSTCKTransactionParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 0806F2F31DBB9E7100C2741B /* PSTCKTransactionParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 08A572421DF92F3F0045E184 /* PSTCKValidationParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 0806F2FC1DBBA3F600C2741B /* PSTCKValidationParams.h */; }; + 08A572431DF92F3F0045E184 /* PSTCKTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 0806F2F21DBB9E7100C2741B /* PSTCKTransaction.h */; }; + 08C260A91E9C214A002AE28C /* PSTCKAuthViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 08C260A71E9C214A002AE28C /* PSTCKAuthViewController.h */; }; + 08C260AA1E9C214A002AE28C /* PSTCKAuthViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 08C260A71E9C214A002AE28C /* PSTCKAuthViewController.h */; }; + 08C260AB1E9C214A002AE28C /* PSTCKAuthViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 08C260A71E9C214A002AE28C /* PSTCKAuthViewController.h */; }; + 08C260AC1E9C214A002AE28C /* PSTCKAuthViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 08C260A71E9C214A002AE28C /* PSTCKAuthViewController.h */; }; + 08C260AD1E9C214A002AE28C /* PSTCKAuthViewController.h in Copy Files */ = {isa = PBXBuildFile; fileRef = 08C260A71E9C214A002AE28C /* PSTCKAuthViewController.h */; }; + 08C260AE1E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 08C260A81E9C214A002AE28C /* PSTCKAuthViewController.m */; }; + 08C260AF1E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 08C260A81E9C214A002AE28C /* PSTCKAuthViewController.m */; }; + 08C260B01E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 08C260A81E9C214A002AE28C /* PSTCKAuthViewController.m */; }; + 08C260B11E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 08C260A81E9C214A002AE28C /* PSTCKAuthViewController.m */; }; + 08C260B21E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 08C260A81E9C214A002AE28C /* PSTCKAuthViewController.m */; }; + 08C260B31E9C214A002AE28C /* PSTCKAuthViewController.m in Copy Files */ = {isa = PBXBuildFile; fileRef = 08C260A81E9C214A002AE28C /* PSTCKAuthViewController.m */; }; 101987811CA47DAC0089A68A /* PSTCKRSA.m in Sources */ = {isa = PBXBuildFile; fileRef = 10500AB11C8135D800EEF7CF /* PSTCKRSA.m */; }; 10500AB21C8135D800EEF7CF /* PSTCKRSA.m in Sources */ = {isa = PBXBuildFile; fileRef = 10500AB11C8135D800EEF7CF /* PSTCKRSA.m */; }; - 10500AB51C8136EF00EEF7CF /* PSTCKRSATest.m in Sources */ = {isa = PBXBuildFile; fileRef = 10500AB41C8136EF00EEF7CF /* PSTCKRSATest.m */; }; 10500ABB1C82155B00EEF7CF /* PSTCKRSA.h in Headers */ = {isa = PBXBuildFile; fileRef = 10500AB31C8135F800EEF7CF /* PSTCKRSA.h */; settings = {ATTRIBUTES = (Public, ); }; }; 10500ABC1C82157A00EEF7CF /* PSTCKRSA.m in Sources */ = {isa = PBXBuildFile; fileRef = 10500AB11C8135D800EEF7CF /* PSTCKRSA.m */; }; 10500ABD1C8215F400EEF7CF /* PSTCKRSA.h in Headers */ = {isa = PBXBuildFile; fileRef = 10500AB31C8135F800EEF7CF /* PSTCKRSA.h */; settings = {ATTRIBUTES = (Public, ); }; }; 10500ABE1C8216C200EEF7CF /* PSTCKRSA.h in Headers */ = {isa = PBXBuildFile; fileRef = 10500AB31C8135F800EEF7CF /* PSTCKRSA.h */; }; 10A653A31C88CC5900EBC974 /* PSTCKCardParams.h in Headers */ = {isa = PBXBuildFile; fileRef = 04CDE5BB1BC1F21500548833 /* PSTCKCardParams.h */; settings = {ATTRIBUTES = (Public, ); }; }; 10FC52071C88DDB3004A0733 /* PSTCKRSA.h in Headers */ = {isa = PBXBuildFile; fileRef = 10500AB31C8135F800EEF7CF /* PSTCKRSA.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 919A9FBB249C442300B7A571 /* PSTCKAPIClientExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 919A9FBA249C442300B7A571 /* PSTCKAPIClientExtension.swift */; }; + 91E448EE24B4A8DC007AA8F4 /* PSTCKAddressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91E448ED24B4A8DC007AA8F4 /* PSTCKAddressViewController.swift */; }; + 91E448F824B60883007AA8F4 /* AddressViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 91E448F724B60883007AA8F4 /* AddressViewController.xib */; }; + 91FEDC5224B69EF000A236BA /* ValidatorDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FEDC4A24B69EF000A236BA /* ValidatorDictionary.swift */; }; + 91FEDC5324B69EF000A236BA /* Validator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FEDC4B24B69EF000A236BA /* Validator.swift */; }; + 91FEDC5424B69EF000A236BA /* ValidationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FEDC4C24B69EF000A236BA /* ValidationDelegate.swift */; }; + 91FEDC5524B69EF000A236BA /* Validatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FEDC4D24B69EF000A236BA /* Validatable.swift */; }; + 91FEDC5624B69EF000A236BA /* Rule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FEDC4E24B69EF000A236BA /* Rule.swift */; }; + 91FEDC5724B69EF000A236BA /* RequiredRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FEDC4F24B69EF000A236BA /* RequiredRule.swift */; }; + 91FEDC5824B69EF000A236BA /* ValidationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FEDC5024B69EF000A236BA /* ValidationError.swift */; }; + 91FEDC5924B69EF000A236BA /* ValidationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FEDC5124B69EF000A236BA /* ValidationRule.swift */; }; + 91FEDC5B24B6A3DF00A236BA /* ButtonExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FEDC5A24B6A3DF00A236BA /* ButtonExtension.swift */; }; + 91FEDC6124B71FD100A236BA /* Cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = 91FEDC5E24B71FD000A236BA /* Cancel.png */; }; + 91FEDC6224B71FD100A236BA /* Cancel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 91FEDC5F24B71FD000A236BA /* Cancel@2x.png */; }; + 91FEDC6324B71FD100A236BA /* Cancel@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 91FEDC6024B71FD000A236BA /* Cancel@3x.png */; }; + 91FEDC6524B72B9800A236BA /* KeyboardHandlingVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91FEDC6424B72B9800A236BA /* KeyboardHandlingVC.swift */; }; C1718D561C3B2E5B002A7CB3 /* UIImage+Paystack.h in Headers */ = {isa = PBXBuildFile; fileRef = C1718D541C3B2E5B002A7CB3 /* UIImage+Paystack.h */; }; C1718D571C3B2E5B002A7CB3 /* UIImage+Paystack.m in Sources */ = {isa = PBXBuildFile; fileRef = C1718D551C3B2E5B002A7CB3 /* UIImage+Paystack.m */; }; C1718D581C3B2E60002A7CB3 /* UIImage+Paystack.h in Headers */ = {isa = PBXBuildFile; fileRef = C1718D541C3B2E5B002A7CB3 /* UIImage+Paystack.h */; }; @@ -292,11 +325,13 @@ }; 04B33F321BC744D100DD8120 /* Copy Files */ = { isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; + buildActionMask = 12; dstPath = Paystack.framework; dstSubfolderSpec = 16; files = ( 04B33F361BC7488D00DD8120 /* Info.plist in Copy Files */, + 08C260B31E9C214A002AE28C /* PSTCKAuthViewController.m in Copy Files */, + 08C260AD1E9C214A002AE28C /* PSTCKAuthViewController.h in Copy Files */, ); name = "Copy Files"; runOnlyForDeploymentPostprocessing = 0; @@ -304,7 +339,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 0401C6E61BA8E3C300CE8A6D /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = Paystack/module.modulemap; sourceTree = SOURCE_ROOT; }; 042CA1B31B7BD84100AF0DA6 /* pstck_card_placeholder_template.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pstck_card_placeholder_template.png; sourceTree = ""; }; 042CA1B41B7BD84100AF0DA6 /* pstck_card_placeholder_template@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pstck_card_placeholder_template@2x.png"; sourceTree = ""; }; 042CA1B51B7BD84100AF0DA6 /* pstck_card_placeholder_template@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pstck_card_placeholder_template@3x.png"; sourceTree = ""; }; @@ -353,14 +387,11 @@ 049952CE1BCF13510088C703 /* PSTCKAPIPostRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKAPIPostRequest.m; sourceTree = ""; }; 049952D11BCF13DD0088C703 /* PSTCKAPIClient+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "PSTCKAPIClient+Private.h"; sourceTree = ""; }; 049E84AB1A605D93000B66CD /* libPaystack.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPaystack.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 04A58A461BC603BB004E7BC2 /* FABKitProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FABKitProtocol.h; sourceTree = ""; }; - 04A58A471BC603BB004E7BC2 /* Fabric+FABKits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Fabric+FABKits.h"; sourceTree = ""; }; - 04A58A481BC603BB004E7BC2 /* Fabric.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Fabric.h; sourceTree = ""; }; 04B33F301BC7417B00DD8120 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 04B94BC71A47B78A00092C46 /* AddressBook.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AddressBook.framework; path = System/Library/Frameworks/AddressBook.framework; sourceTree = SDKROOT; }; 04CDB4421A5F2E1800B854EE /* Paystack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Paystack.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 04CDB4A91A5F30A700B854EE /* Paystack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Paystack.h; path = PublicHeaders/Paystack.h; sourceTree = ""; }; - 04CDB4C21A5F30A700B854EE /* PSTCKAPIClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = PSTCKAPIClient.h; path = PublicHeaders/PSTCKAPIClient.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 04CDB4C21A5F30A700B854EE /* PSTCKAPIClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = PSTCKAPIClient.h; path = PublicHeaders/PSTCKAPIClient.h; sourceTree = ""; }; 04CDB4C31A5F30A700B854EE /* PSTCKAPIClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKAPIClient.m; sourceTree = ""; }; 04CDB4C41A5F30A700B854EE /* PSTCKFormEncoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSTCKFormEncoder.h; sourceTree = ""; }; 04CDB4C51A5F30A700B854EE /* PSTCKFormEncoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKFormEncoder.m; sourceTree = ""; }; @@ -371,10 +402,6 @@ 04CDB4CE1A5F30A700B854EE /* PaystackError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PaystackError.h; path = PublicHeaders/PaystackError.h; sourceTree = ""; }; 04CDB4CF1A5F30A700B854EE /* PaystackError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PaystackError.m; sourceTree = ""; }; 04CDB51E1A5F3A9300B854EE /* PSTCKAPIClientTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKAPIClientTest.m; sourceTree = ""; }; - 04CDB51F1A5F3A9300B854EE /* PSTCKFormEncoderTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKFormEncoderTest.m; sourceTree = ""; }; - 04CDB5241A5F3A9300B854EE /* PSTCKCardFunctionalTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKCardFunctionalTest.m; sourceTree = ""; }; - 04CDB5251A5F3A9300B854EE /* PSTCKCardTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKCardTest.m; sourceTree = ""; }; - 04CDB5261A5F3A9300B854EE /* PSTCKCertTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKCertTest.m; sourceTree = ""; }; 04CDB5271A5F3A9300B854EE /* PSTCKTokenTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKTokenTest.m; sourceTree = ""; }; 04CDE5B41BC1F1F100548833 /* PSTCKCardParams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKCardParams.m; sourceTree = ""; }; 04CDE5BB1BC1F21500548833 /* PSTCKCardParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PSTCKCardParams.h; path = PublicHeaders/PSTCKCardParams.h; sourceTree = ""; }; @@ -405,11 +432,37 @@ 04F39F241AEF2AFE005B926E /* PaystackOSXTests-Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "PaystackOSXTests-Shared.xcconfig"; sourceTree = ""; }; 04FCFA171BD59A8C00297732 /* PSTCKCategoryLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PSTCKCategoryLoader.h; sourceTree = ""; }; 04FCFA181BD59A8C00297732 /* PSTCKCategoryLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKCategoryLoader.m; sourceTree = ""; }; + 0806F2F21DBB9E7100C2741B /* PSTCKTransaction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PSTCKTransaction.h; path = PublicHeaders/PSTCKTransaction.h; sourceTree = ""; }; + 0806F2F31DBB9E7100C2741B /* PSTCKTransactionParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PSTCKTransactionParams.h; path = PublicHeaders/PSTCKTransactionParams.h; sourceTree = ""; }; + 0806F2F61DBB9E8200C2741B /* PSTCKTransaction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKTransaction.m; sourceTree = ""; }; + 0806F2F71DBB9E8200C2741B /* PSTCKTransactionParams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKTransactionParams.m; sourceTree = ""; }; + 0806F2FA1DBBA3C500C2741B /* PSTCKValidationParams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKValidationParams.m; sourceTree = ""; }; + 0806F2FC1DBBA3F600C2741B /* PSTCKValidationParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PSTCKValidationParams.h; path = PublicHeaders/PSTCKValidationParams.h; sourceTree = ""; }; + 0806F2FE1DBBB5DF00C2741B /* pstck_card_verve.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = pstck_card_verve.png; sourceTree = ""; }; + 0806F2FF1DBBB5DF00C2741B /* pstck_card_verve@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pstck_card_verve@2x.png"; sourceTree = ""; }; + 0806F3001DBBB5DF00C2741B /* pstck_card_verve@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "pstck_card_verve@3x.png"; sourceTree = ""; }; + 08C260A71E9C214A002AE28C /* PSTCKAuthViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PSTCKAuthViewController.h; path = UI/PSTCKAuthViewController.h; sourceTree = ""; }; + 08C260A81E9C214A002AE28C /* PSTCKAuthViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PSTCKAuthViewController.m; path = UI/PSTCKAuthViewController.m; sourceTree = ""; }; 10500AB11C8135D800EEF7CF /* PSTCKRSA.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PSTCKRSA.m; path = RSA/PSTCKRSA.m; sourceTree = ""; }; 10500AB31C8135F800EEF7CF /* PSTCKRSA.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PSTCKRSA.h; path = RSA/PSTCKRSA.h; sourceTree = ""; }; - 10500AB41C8136EF00EEF7CF /* PSTCKRSATest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKRSATest.m; sourceTree = ""; }; 11C74B9B164043050071C2CA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 4A0D74F918F6106100966D7B /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 919A9FBA249C442300B7A571 /* PSTCKAPIClientExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PSTCKAPIClientExtension.swift; sourceTree = ""; }; + 91E448ED24B4A8DC007AA8F4 /* PSTCKAddressViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PSTCKAddressViewController.swift; sourceTree = ""; }; + 91E448F724B60883007AA8F4 /* AddressViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AddressViewController.xib; sourceTree = ""; }; + 91FEDC4A24B69EF000A236BA /* ValidatorDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidatorDictionary.swift; sourceTree = ""; }; + 91FEDC4B24B69EF000A236BA /* Validator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Validator.swift; sourceTree = ""; }; + 91FEDC4C24B69EF000A236BA /* ValidationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationDelegate.swift; sourceTree = ""; }; + 91FEDC4D24B69EF000A236BA /* Validatable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Validatable.swift; sourceTree = ""; }; + 91FEDC4E24B69EF000A236BA /* Rule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Rule.swift; sourceTree = ""; }; + 91FEDC4F24B69EF000A236BA /* RequiredRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequiredRule.swift; sourceTree = ""; }; + 91FEDC5024B69EF000A236BA /* ValidationError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationError.swift; sourceTree = ""; }; + 91FEDC5124B69EF000A236BA /* ValidationRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationRule.swift; sourceTree = ""; }; + 91FEDC5A24B6A3DF00A236BA /* ButtonExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonExtension.swift; sourceTree = ""; }; + 91FEDC5E24B71FD000A236BA /* Cancel.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Cancel.png; sourceTree = ""; }; + 91FEDC5F24B71FD000A236BA /* Cancel@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Cancel@2x.png"; sourceTree = ""; }; + 91FEDC6024B71FD000A236BA /* Cancel@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Cancel@3x.png"; sourceTree = ""; }; + 91FEDC6424B72B9800A236BA /* KeyboardHandlingVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardHandlingVC.swift; sourceTree = ""; }; C1718D541C3B2E5B002A7CB3 /* UIImage+Paystack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+Paystack.h"; path = "PublicHeaders/UI/UIImage+Paystack.h"; sourceTree = ""; }; C1718D551C3B2E5B002A7CB3 /* UIImage+Paystack.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+Paystack.m"; path = "UI/UIImage+Paystack.m"; sourceTree = ""; }; C178CD441C45607D00851C69 /* UIImage+PaystackTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+PaystackTest.m"; sourceTree = ""; }; @@ -429,7 +482,6 @@ buildActionMask = 2147483647; files = ( 049E84D71A605E99000B66CD /* AddressBook.framework in Frameworks */, - 049E84D61A605E8F000B66CD /* PassKit.framework in Frameworks */, 049E84D51A605E82000B66CD /* Security.framework in Frameworks */, 049E84D41A605E7C000B66CD /* Foundation.framework in Frameworks */, 049E84D31A605E6A000B66CD /* UIKit.framework in Frameworks */, @@ -440,7 +492,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 04533E7D1A6877F400C7E52E /* PassKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -466,13 +517,19 @@ isa = PBXGroup; children = ( 04E32A9C1B7A9490009C9E35 /* PSTCKPaymentCardTextField.h */, + 08C260A71E9C214A002AE28C /* PSTCKAuthViewController.h */, + 08C260A81E9C214A002AE28C /* PSTCKAuthViewController.m */, 0438EF291B7416BB00D506CC /* PSTCKPaymentCardTextField.m */, + 91E448F724B60883007AA8F4 /* AddressViewController.xib */, + 91E448ED24B4A8DC007AA8F4 /* PSTCKAddressViewController.swift */, 0438EF261B7416BB00D506CC /* PSTCKFormTextField.h */, 0438EF271B7416BB00D506CC /* PSTCKFormTextField.m */, 0438EF2A1B7416BB00D506CC /* PSTCKPaymentCardTextFieldViewModel.h */, 0438EF2B1B7416BB00D506CC /* PSTCKPaymentCardTextFieldViewModel.m */, C1718D541C3B2E5B002A7CB3 /* UIImage+Paystack.h */, C1718D551C3B2E5B002A7CB3 /* UIImage+Paystack.m */, + 91FEDC5A24B6A3DF00A236BA /* ButtonExtension.swift */, + 91FEDC6424B72B9800A236BA /* KeyboardHandlingVC.swift */, ); name = UI; sourceTree = ""; @@ -488,6 +545,12 @@ 0438EF881B741C2800D506CC /* Images */ = { isa = PBXGroup; children = ( + 0806F2FE1DBBB5DF00C2741B /* pstck_card_verve.png */, + 0806F2FF1DBBB5DF00C2741B /* pstck_card_verve@2x.png */, + 91FEDC5E24B71FD000A236BA /* Cancel.png */, + 91FEDC5F24B71FD000A236BA /* Cancel@2x.png */, + 91FEDC6024B71FD000A236BA /* Cancel@3x.png */, + 0806F3001DBBB5DF00C2741B /* pstck_card_verve@3x.png */, 0438EF891B741C2800D506CC /* pstck_card_amex.png */, 0438EF8A1B741C2800D506CC /* pstck_card_amex@2x.png */, 0438EF8B1B741C2800D506CC /* pstck_card_amex@3x.png */, @@ -522,17 +585,6 @@ path = Images; sourceTree = ""; }; - 04A58A451BC603BB004E7BC2 /* Fabric */ = { - isa = PBXGroup; - children = ( - 04A58A461BC603BB004E7BC2 /* FABKitProtocol.h */, - 04A58A471BC603BB004E7BC2 /* Fabric+FABKits.h */, - 04A58A481BC603BB004E7BC2 /* Fabric.h */, - ); - name = Fabric; - path = Paystack/Fabric; - sourceTree = ""; - }; 04B33F2F1BC7414C00DD8120 /* Supporting Files */ = { isa = PBXGroup; children = ( @@ -544,14 +596,16 @@ 04CDB4D21A5F30A700B854EE /* Paystack */ = { isa = PBXGroup; children = ( - 10500AB01C81357A00EEF7CF /* RSA */, 04CDB4A91A5F30A700B854EE /* Paystack.h */, + 10500AB01C81357A00EEF7CF /* RSA */, 04F39F091AEF2AFE005B926E /* BuildConfigurations */, 0438EF871B741C2800D506CC /* Resources */, 0438EF251B74162700D506CC /* UI */, 04B33F2F1BC7414C00DD8120 /* Supporting Files */, + 91FEDC4924B69EF000A236BA /* Validator */, 04CDB4C21A5F30A700B854EE /* PSTCKAPIClient.h */, 04CDB4C31A5F30A700B854EE /* PSTCKAPIClient.m */, + 919A9FBA249C442300B7A571 /* PSTCKAPIClientExtension.swift */, 049952D11BCF13DD0088C703 /* PSTCKAPIClient+Private.h */, 049952CD1BCF13510088C703 /* PSTCKAPIPostRequest.h */, 049952CE1BCF13510088C703 /* PSTCKAPIPostRequest.m */, @@ -567,6 +621,12 @@ 04EBC7511B7533C300A0E6AE /* PSTCKCardValidationState.h */, 04EBC7521B7533C300A0E6AE /* PSTCKCardValidator.h */, 0438EF3F1B74170D00D506CC /* PSTCKCardValidator.m */, + 0806F2F61DBB9E8200C2741B /* PSTCKTransaction.m */, + 0806F2F31DBB9E7100C2741B /* PSTCKTransactionParams.h */, + 0806F2F71DBB9E8200C2741B /* PSTCKTransactionParams.m */, + 0806F2FC1DBBA3F600C2741B /* PSTCKValidationParams.h */, + 0806F2FA1DBBA3C500C2741B /* PSTCKValidationParams.m */, + 0806F2F21DBB9E7100C2741B /* PSTCKTransaction.h */, 04CDB4CC1A5F30A700B854EE /* PSTCKToken.h */, 04CDB4CD1A5F30A700B854EE /* PSTCKToken.m */, 04CDB4CE1A5F30A700B854EE /* PaystackError.h */, @@ -583,17 +643,12 @@ 04CDB5281A5F3A9300B854EE /* PaystackTests */ = { isa = PBXGroup; children = ( - 04CDB51F1A5F3A9300B854EE /* PSTCKFormEncoderTest.m */, 04CDB51E1A5F3A9300B854EE /* PSTCKAPIClientTest.m */, - 04CDB5241A5F3A9300B854EE /* PSTCKCardFunctionalTest.m */, 0438EF4A1B741B0100D506CC /* PSTCKCardValidatorTest.m */, 045A62AA1B8E7259000165CE /* PSTCKPaymentCardTextFieldTest.m */, 0438EF4B1B741B0100D506CC /* PSTCKPaymentCardTextFieldViewModelTest.m */, - 04CDB5251A5F3A9300B854EE /* PSTCKCardTest.m */, - 04CDB5261A5F3A9300B854EE /* PSTCKCertTest.m */, 04CDB5271A5F3A9300B854EE /* PSTCKTokenTest.m */, C178CD441C45607D00851C69 /* UIImage+PaystackTest.m */, - 10500AB41C8136EF00EEF7CF /* PSTCKRSATest.m */, ); name = PaystackTests; path = Tests/Tests; @@ -602,7 +657,6 @@ 04F39F091AEF2AFE005B926E /* BuildConfigurations */ = { isa = PBXGroup; children = ( - 0401C6E61BA8E3C300CE8A6D /* module.modulemap */, 04F39F0C1AEF2AFE005B926E /* Project-Shared.xcconfig */, 04F39F0A1AEF2AFE005B926E /* Project-Debug.xcconfig */, 04F39F0B1AEF2AFE005B926E /* Project-Release.xcconfig */, @@ -658,7 +712,6 @@ 11C74B9A164043050071C2CA /* Frameworks */ = { isa = PBXGroup; children = ( - 04A58A451BC603BB004E7BC2 /* Fabric */, 04365D2C1A4CF86C00A3E1D4 /* CoreGraphics.framework */, 04B94BC71A47B78A00092C46 /* AddressBook.framework */, 04D5BF9019BF958F009521A5 /* PassKit.framework */, @@ -669,6 +722,21 @@ name = Frameworks; sourceTree = ""; }; + 91FEDC4924B69EF000A236BA /* Validator */ = { + isa = PBXGroup; + children = ( + 91FEDC4A24B69EF000A236BA /* ValidatorDictionary.swift */, + 91FEDC4B24B69EF000A236BA /* Validator.swift */, + 91FEDC4C24B69EF000A236BA /* ValidationDelegate.swift */, + 91FEDC4D24B69EF000A236BA /* Validatable.swift */, + 91FEDC4E24B69EF000A236BA /* Rule.swift */, + 91FEDC4F24B69EF000A236BA /* RequiredRule.swift */, + 91FEDC5024B69EF000A236BA /* ValidationError.swift */, + 91FEDC5124B69EF000A236BA /* ValidationRule.swift */, + ); + path = Validator; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -686,6 +754,7 @@ 10500ABE1C8216C200EEF7CF /* PSTCKRSA.h in Headers */, 04415C721A6605D9001225ED /* Paystack.h in Headers */, 04415C851A6605D9001225ED /* PaystackError.h in Headers */, + 08C260AA1E9C214A002AE28C /* PSTCKAuthViewController.h in Headers */, 04415C7F1A6605D9001225ED /* PSTCKAPIClient.h in Headers */, 04CDE5BF1BC1FD4500548833 /* PSTCKCardParams.h in Headers */, 0438EF391B7416BB00D506CC /* PSTCKPaymentCardTextFieldViewModel.h in Headers */, @@ -702,15 +771,19 @@ 049E84D91A605EF0000B66CD /* Paystack.h in Headers */, 0433EB4B1BD06313003912B4 /* NSDictionary+Paystack.h in Headers */, 0438EF2E1B7416BB00D506CC /* PSTCKFormTextField.h in Headers */, + 08C260AC1E9C214A002AE28C /* PSTCKAuthViewController.h in Headers */, 049E84E61A605EF0000B66CD /* PSTCKAPIClient.h in Headers */, 049E84EA1A605EF0000B66CD /* PSTCKCard.h in Headers */, 049E84EB1A605EF0000B66CD /* PSTCKToken.h in Headers */, 04E32AA01B7A9490009C9E35 /* PSTCKPaymentCardTextField.h in Headers */, 0438EF491B74183100D506CC /* PSTCKCardBrand.h in Headers */, 10500ABD1C8215F400EEF7CF /* PSTCKRSA.h in Headers */, + 08A572411DF92F3F0045E184 /* PSTCKTransactionParams.h in Headers */, 049952D61BCF14930088C703 /* PSTCKAPIPostRequest.h in Headers */, 04F213331BCEAB61001D6F22 /* PSTCKFormEncodable.h in Headers */, 049E84EC1A605EF0000B66CD /* PaystackError.h in Headers */, + 08A572421DF92F3F0045E184 /* PSTCKValidationParams.h in Headers */, + 08A572431DF92F3F0045E184 /* PSTCKTransaction.h in Headers */, C1718D581C3B2E60002A7CB3 /* UIImage+Paystack.h in Headers */, 04FCFA191BD59A8C00297732 /* PSTCKCategoryLoader.h in Headers */, 049952D41BCF13DD0088C703 /* PSTCKAPIClient+Private.h in Headers */, @@ -730,7 +803,9 @@ 0433EB491BD06313003912B4 /* NSDictionary+Paystack.h in Headers */, 04CDB50E1A5F30A700B854EE /* PSTCKCard.h in Headers */, 0438EF2C1B7416BB00D506CC /* PSTCKFormTextField.h in Headers */, + 08C260A91E9C214A002AE28C /* PSTCKAuthViewController.h in Headers */, 049952D21BCF13DD0088C703 /* PSTCKAPIClient+Private.h in Headers */, + 0806F2F51DBB9E7100C2741B /* PSTCKTransactionParams.h in Headers */, C1718D561C3B2E5B002A7CB3 /* UIImage+Paystack.h in Headers */, 04CDB5121A5F30A700B854EE /* PSTCKToken.h in Headers */, 049952CF1BCF13510088C703 /* PSTCKAPIPostRequest.h in Headers */, @@ -738,7 +813,10 @@ 10A653A31C88CC5900EBC974 /* PSTCKCardParams.h in Headers */, 10FC52071C88DDB3004A0733 /* PSTCKRSA.h in Headers */, 04CDB5161A5F30A700B854EE /* PaystackError.h in Headers */, + 08A572401DF92F1D0045E184 /* PSTCKCategoryLoader.h in Headers */, 04E32A9D1B7A9490009C9E35 /* PSTCKPaymentCardTextField.h in Headers */, + 0806F2F41DBB9E7100C2741B /* PSTCKTransaction.h in Headers */, + 0806F2FD1DBBA3F600C2741B /* PSTCKValidationParams.h in Headers */, 04F213311BCEAB61001D6F22 /* PSTCKFormEncodable.h in Headers */, 04CDB5021A5F30A700B854EE /* PSTCKFormEncoder.h in Headers */, 0438EF381B7416BB00D506CC /* PSTCKPaymentCardTextFieldViewModel.h in Headers */, @@ -768,6 +846,7 @@ 0433EB4A1BD06313003912B4 /* NSDictionary+Paystack.h in Headers */, 04F213361BCECB1C001D6F22 /* PSTCKAPIResponseDecodable.h in Headers */, 04D12C3A1A5F55D10010446E /* PSTCKFormEncoder.h in Headers */, + 08C260AB1E9C214A002AE28C /* PSTCKAuthViewController.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -874,7 +953,7 @@ attributes = { CLASSPREFIX = PSTCK; LastTestingUpgradeCheck = 0510; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 1030; ORGANIZATIONNAME = "Paystack, Inc"; TargetAttributes = { 045E7C021A5F41DE004751EF = { @@ -890,7 +969,7 @@ }; 04CDB4411A5F2E1800B854EE = { CreatedOnToolsVersion = 6.1.1; - LastSwiftMigration = 0800; + LastSwiftMigration = 1150; }; 04D12C051A5F556D0010446E = { CreatedOnToolsVersion = 6.1.1; @@ -901,11 +980,12 @@ }; }; buildConfigurationList = 11C74B92164043050071C2CA /* Build configuration list for PBXProject "Paystack" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + compatibilityVersion = "Xcode 6.3"; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = 11C74B8D164043050071C2CA; productRefGroup = 11C74B99164043050071C2CA /* Products */; @@ -969,6 +1049,7 @@ 0438EFC21B741C2800D506CC /* pstck_card_jcb.png in Resources */, 042CA1B61B7BD84100AF0DA6 /* pstck_card_placeholder_template.png in Resources */, 0438EFD61B741C2800D506CC /* pstck_card_visa@2x.png in Resources */, + 91E448F824B60883007AA8F4 /* AddressViewController.xib in Resources */, 0438EFD21B741C2800D506CC /* pstck_card_placeholder@3x.png in Resources */, 0438EFB01B741C2800D506CC /* pstck_card_cvc_amex.png in Resources */, 042CA1B71B7BD84100AF0DA6 /* pstck_card_placeholder_template@2x.png in Resources */, @@ -983,13 +1064,19 @@ 0438EFBE1B741C2800D506CC /* pstck_card_discover@2x.png in Resources */, 0438EFC01B741C2800D506CC /* pstck_card_discover@3x.png in Resources */, 0438EFB41B741C2800D506CC /* pstck_card_cvc_amex@3x.png in Resources */, + 91FEDC6224B71FD100A236BA /* Cancel@2x.png in Resources */, 0438EFB81B741C2800D506CC /* pstck_card_diners@2x.png in Resources */, + 91FEDC6324B71FD100A236BA /* Cancel@3x.png in Resources */, + 0806F3031DBBB5DF00C2741B /* pstck_card_verve@3x.png in Resources */, 0438EFAA1B741C2800D506CC /* pstck_card_cvc.png in Resources */, 0438EFAC1B741C2800D506CC /* pstck_card_cvc@2x.png in Resources */, 0438EFCC1B741C2800D506CC /* pstck_card_mastercard@3x.png in Resources */, + 0806F3011DBBB5DF00C2741B /* pstck_card_verve.png in Resources */, 0438EFCE1B741C2800D506CC /* pstck_card_placeholder.png in Resources */, + 0806F3021DBBB5DF00C2741B /* pstck_card_verve@2x.png in Resources */, 0438EFCA1B741C2800D506CC /* pstck_card_mastercard@2x.png in Resources */, 042CA1B81B7BD84100AF0DA6 /* pstck_card_placeholder_template@3x.png in Resources */, + 91FEDC6124B71FD100A236BA /* Cancel.png in Resources */, 0438EFC81B741C2800D506CC /* pstck_card_mastercard.png in Resources */, 0438EFBC1B741C2800D506CC /* pstck_card_discover.png in Resources */, 0438EFA61B741C2800D506CC /* pstck_card_amex@2x.png in Resources */, @@ -1067,7 +1154,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\n\n# If we're already inside this script then die\nif [ -n \"$RW_MULTIPLATFORM_BUILD_IN_PROGRESS\" ]; then\nexit 0\nfi\nexport RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1\n\nRW_FRAMEWORK_NAME=\"Paystack\"\nRW_INPUT_STATIC_LIB=\"libPaystack.a\"\nRW_FRAMEWORK_LOCATION=\"${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework\"\n\nfunction build_static_library {\n # Will rebuild the static library as specified\n # build_static_library sdk\n xcrun xcodebuild -project \"${PROJECT_FILE_PATH}\" \\\n -target \"${TARGET_NAME}\" \\\n -configuration \"${CONFIGURATION}\" \\\n -sdk \"${1}\" \\\n ONLY_ACTIVE_ARCH=NO \\\n BUILD_DIR=\"${BUILD_DIR}\" \\\n OBJROOT=\"${OBJROOT}\" \\\n BUILD_ROOT=\"${BUILD_ROOT}\" \\\n SYMROOT=\"${SYMROOT}\" $ACTION\n}\n\nfunction make_fat_library {\n # Will smash 2 static libs together\n # make_fat_library in1 in2 out\n xcrun lipo -create \"${1}\" \"${2}\" -output \"${3}\"\n}\n\n# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK name\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]; then\nRW_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\n# 2 - Extract the version from the SDK\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]; then\nRW_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\n# 3 - Determine the other platform\nif [ \"$RW_SDK_PLATFORM\" == \"iphoneos\" ]; then\nRW_OTHER_PLATFORM=iphonesimulator\nelse\nRW_OTHER_PLATFORM=iphoneos\nfi\n\n# 4 - Find the build directory\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$RW_SDK_PLATFORM$ ]]; then\nRW_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}\"\nelse\necho \"Could not find other platform build directory.\"\nexit 1\nfi\n\n# Build the other platform.\nbuild_static_library \"${RW_OTHER_PLATFORM}${RW_SDK_VERSION}\"\n\n# If we're currently building for iphonesimulator, then need to rebuild\n# to ensure that we get both i386 and x86_64\nif [ \"$RW_SDK_PLATFORM\" == \"iphonesimulator\" ]; then\nbuild_static_library \"${SDK_NAME}\"\nfi\n\n# Join the 2 static libs into 1 and push into the .framework\nmake_fat_library \"${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}\" \\\n\"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}\" \\\n\"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}\"\n\n/usr/bin/strip -Sx \"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}\"\n\n# Ensure that the framework is present in both platform's build directories\ncp -a \"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}\" \\\n\"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}\""; + shellScript = "set -e\n\n# If we're already inside this script then die\nif [ -n \"$RW_MULTIPLATFORM_BUILD_IN_PROGRESS\" ]; then\nexit 0\nfi\nexport RW_MULTIPLATFORM_BUILD_IN_PROGRESS=1\n\nRW_FRAMEWORK_NAME=\"Paystack\"\nRW_INPUT_STATIC_LIB=\"libPaystack.a\"\nRW_FRAMEWORK_LOCATION=\"${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework\"\n\nfunction build_static_library {\n # Will rebuild the static library as specified\n # build_static_library sdk\n xcrun xcodebuild -project \"${PROJECT_FILE_PATH}\" \\\n -target \"${TARGET_NAME}\" \\\n -configuration \"${CONFIGURATION}\" \\\n -sdk \"${1}\" \\\n ONLY_ACTIVE_ARCH=NO \\\n BUILD_DIR=\"${BUILD_DIR}\" \\\n OBJROOT=\"${OBJROOT}/DependentBuilds\" \\\n BUILD_ROOT=\"${BUILD_ROOT}\" \\\n SYMROOT=\"${SYMROOT}\" $ACTION\n}\n\nfunction make_fat_library {\n # Will smash 2 static libs together\n # make_fat_library in1 in2 out\n xcrun lipo -create \"${1}\" \"${2}\" -output \"${3}\"\n}\n\n# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK name\nif [[ \"$SDK_NAME\" =~ ([A-Za-z]+) ]]; then\nRW_SDK_PLATFORM=${BASH_REMATCH[1]}\nelse\necho \"Could not find platform name from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\n# 2 - Extract the version from the SDK\nif [[ \"$SDK_NAME\" =~ ([0-9]+.*$) ]]; then\nRW_SDK_VERSION=${BASH_REMATCH[1]}\nelse\necho \"Could not find sdk version from SDK_NAME: $SDK_NAME\"\nexit 1\nfi\n\n# 3 - Determine the other platform\nif [ \"$RW_SDK_PLATFORM\" == \"iphoneos\" ]; then\nRW_OTHER_PLATFORM=iphonesimulator\nelse\nRW_OTHER_PLATFORM=iphoneos\nfi\n\n# 4 - Find the build directory\nif [[ \"$BUILT_PRODUCTS_DIR\" =~ (.*)$RW_SDK_PLATFORM$ ]]; then\nRW_OTHER_BUILT_PRODUCTS_DIR=\"${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}\"\nelse\necho \"Could not find other platform build directory.\"\nexit 1\nfi\n\n# Build the other platform.\nbuild_static_library \"${RW_OTHER_PLATFORM}${RW_SDK_VERSION}\"\n\n# If we're currently building for iphonesimulator, then need to rebuild\n# to ensure that we get both i386 and x86_64\nif [ \"$RW_SDK_PLATFORM\" == \"iphonesimulator\" ]; then\nbuild_static_library \"${SDK_NAME}\"\nfi\n\n# Join the 2 static libs into 1 and push into the .framework\nmake_fat_library \"${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}\" \\\n\"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}\" \\\n\"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}\"\n\n/usr/bin/strip -Sx \"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}\"\n\n# Ensure that the framework is present in both platform's build directories\ncp -a \"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}\" \\\n\"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}\"\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -1086,14 +1173,10 @@ 0438EF4D1B741B0100D506CC /* PSTCKPaymentCardTextFieldViewModelTest.m in Sources */, 04415C641A6605B5001225ED /* PSTCKCard.m in Sources */, 04415C651A6605B5001225ED /* PSTCKToken.m in Sources */, + 08C260AF1E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */, 04415C661A6605B5001225ED /* PaystackError.m in Sources */, - 10500AB51C8136EF00EEF7CF /* PSTCKRSATest.m in Sources */, 04415C671A6605B5001225ED /* PSTCKAPIClientTest.m in Sources */, - 04415C681A6605B5001225ED /* PSTCKFormEncoderTest.m in Sources */, 0438EF361B7416BB00D506CC /* PSTCKPaymentCardTextField.m in Sources */, - 04415C6D1A6605B5001225ED /* PSTCKCardFunctionalTest.m in Sources */, - 04415C6E1A6605B5001225ED /* PSTCKCardTest.m in Sources */, - 04415C6F1A6605B5001225ED /* PSTCKCertTest.m in Sources */, 04415C701A6605B5001225ED /* PSTCKTokenTest.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1102,6 +1185,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 08A5723C1DF92EC10045E184 /* PSTCKTransaction.m in Sources */, + 08A5723D1DF92EC10045E184 /* PSTCKTransactionParams.m in Sources */, + 08A5723E1DF92EC10045E184 /* PSTCKValidationParams.m in Sources */, 101987811CA47DAC0089A68A /* PSTCKRSA.m in Sources */, 0438EF451B74170D00D506CC /* PSTCKCardValidator.m in Sources */, 0438EF311B7416BB00D506CC /* PSTCKFormTextField.m in Sources */, @@ -1117,6 +1203,7 @@ 049E84D11A605DE0000B66CD /* PSTCKToken.m in Sources */, 049E84D21A605DE0000B66CD /* PaystackError.m in Sources */, 049952D81BCF14990088C703 /* PSTCKAPIPostRequest.m in Sources */, + 08C260B21E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1124,20 +1211,37 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 91FEDC6524B72B9800A236BA /* KeyboardHandlingVC.swift in Sources */, + 08A5723F1DF92EF30045E184 /* PSTCKCategoryLoader.m in Sources */, 0438EF431B74170D00D506CC /* PSTCKCardValidator.m in Sources */, 0438EF2F1B7416BB00D506CC /* PSTCKFormTextField.m in Sources */, + 91FEDC5724B69EF000A236BA /* RequiredRule.swift in Sources */, + 919A9FBB249C442300B7A571 /* PSTCKAPIClientExtension.swift in Sources */, + 91FEDC5224B69EF000A236BA /* ValidatorDictionary.swift in Sources */, + 91FEDC5524B69EF000A236BA /* Validatable.swift in Sources */, + 91FEDC5324B69EF000A236BA /* Validator.swift in Sources */, + 91FEDC5924B69EF000A236BA /* ValidationRule.swift in Sources */, + 0806F2F91DBB9E8200C2741B /* PSTCKTransactionParams.m in Sources */, + 91FEDC5824B69EF000A236BA /* ValidationError.swift in Sources */, 04CDB5101A5F30A700B854EE /* PSTCKCard.m in Sources */, 04CDB5001A5F30A700B854EE /* PSTCKAPIClient.m in Sources */, 10500AB21C8135D800EEF7CF /* PSTCKRSA.m in Sources */, + 0806F2FB1DBBA3C500C2741B /* PSTCKValidationParams.m in Sources */, 04CDB5181A5F30A700B854EE /* PaystackError.m in Sources */, C1718D571C3B2E5B002A7CB3 /* UIImage+Paystack.m in Sources */, + 0806F2F81DBB9E8200C2741B /* PSTCKTransaction.m in Sources */, + 91E448EE24B4A8DC007AA8F4 /* PSTCKAddressViewController.swift in Sources */, 0438EF351B7416BB00D506CC /* PSTCKPaymentCardTextField.m in Sources */, 04CDB5041A5F30A700B854EE /* PSTCKFormEncoder.m in Sources */, + 91FEDC5424B69EF000A236BA /* ValidationDelegate.swift in Sources */, 04CDB5141A5F30A700B854EE /* PSTCKToken.m in Sources */, 0433EB4C1BD06313003912B4 /* NSDictionary+Paystack.m in Sources */, 0438EF3B1B7416BB00D506CC /* PSTCKPaymentCardTextFieldViewModel.m in Sources */, 04CDE5B81BC1F1F100548833 /* PSTCKCardParams.m in Sources */, + 91FEDC5624B69EF000A236BA /* Rule.swift in Sources */, + 91FEDC5B24B6A3DF00A236BA /* ButtonExtension.swift in Sources */, 049952D01BCF13510088C703 /* PSTCKAPIPostRequest.m in Sources */, + 08C260AE1E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1152,6 +1256,7 @@ 04D12C261A5F55AD0010446E /* PSTCKFormEncoder.m in Sources */, 04D12C291A5F55AD0010446E /* PSTCKCard.m in Sources */, 04D12C2A1A5F55AD0010446E /* PSTCKToken.m in Sources */, + 08C260B01E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */, 0433EB4D1BD06313003912B4 /* NSDictionary+Paystack.m in Sources */, 049952D71BCF14980088C703 /* PSTCKAPIPostRequest.m in Sources */, 04D12C2B1A5F55AD0010446E /* PaystackError.m in Sources */, @@ -1163,11 +1268,8 @@ buildActionMask = 2147483647; files = ( 04D12C401A5F55FA0010446E /* PSTCKAPIClientTest.m in Sources */, - 04D12C411A5F55FA0010446E /* PSTCKFormEncoderTest.m in Sources */, - 04D12C451A5F55FA0010446E /* PSTCKCardFunctionalTest.m in Sources */, - 04D12C461A5F55FA0010446E /* PSTCKCardTest.m in Sources */, - 04D12C471A5F55FA0010446E /* PSTCKCertTest.m in Sources */, 04D12C481A5F55FA0010446E /* PSTCKTokenTest.m in Sources */, + 08C260B11E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1196,6 +1298,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 04F39F0D1AEF2AFE005B926E /* PaystackiOS Tests-Debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; }; name = Debug; }; @@ -1203,6 +1306,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 04F39F0E1AEF2AFE005B926E /* PaystackiOS Tests-Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; }; name = Release; }; @@ -1228,7 +1332,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 04F39F181AEF2AFE005B926E /* PaystackiOSStaticFramework.xcconfig */; buildSettings = { - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 1.0; }; name = Debug; }; @@ -1236,7 +1340,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 04F39F181AEF2AFE005B926E /* PaystackiOSStaticFramework.xcconfig */; buildSettings = { - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 1.0; }; name = Release; }; @@ -1244,9 +1348,18 @@ isa = XCBuildConfiguration; baseConfigurationReference = 04F39F101AEF2AFE005B926E /* PaystackiOS-Debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 3.0.17; PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.paystack-ios"; - SWIFT_VERSION = 2.3; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -1254,9 +1367,17 @@ isa = XCBuildConfiguration; baseConfigurationReference = 04F39F111AEF2AFE005B926E /* PaystackiOS-Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + MARKETING_VERSION = 3.0.17; PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.paystack-ios"; - SWIFT_VERSION = 2.3; + SWIFT_OBJC_BRIDGING_HEADER = ""; + SWIFT_VERSION = 5.0; }; name = Release; }; @@ -1264,6 +1385,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 04F39F1F1AEF2AFE005B926E /* PaystackOSX-Debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.paystack-ios"; }; @@ -1273,6 +1395,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 04F39F201AEF2AFE005B926E /* PaystackOSX-Release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; APPLICATION_EXTENSION_API_ONLY = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.paystack-ios"; }; @@ -1296,12 +1419,20 @@ isa = XCBuildConfiguration; baseConfigurationReference = 04F39F0A1AEF2AFE005B926E /* Project-Debug.xcconfig */; buildSettings = { + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; + DEFINES_MODULE = YES; ENABLE_TESTABILITY = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 10.7; ONLY_ACTIVE_ARCH = YES; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -1309,10 +1440,18 @@ isa = XCBuildConfiguration; baseConfigurationReference = 04F39F0B1AEF2AFE005B926E /* Project-Release.xcconfig */; buildSettings = { + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + DEFINES_MODULE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MACOSX_DEPLOYMENT_TARGET = 10.7; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/Paystack.xcodeproj/xcshareddata/xcschemes/PaystackOSX Tests.xcscheme b/Paystack.xcodeproj/xcshareddata/xcschemes/PaystackOSX Tests.xcscheme index 302ddf7..fceba2f 100644 --- a/Paystack.xcodeproj/xcshareddata/xcschemes/PaystackOSX Tests.xcscheme +++ b/Paystack.xcodeproj/xcshareddata/xcschemes/PaystackOSX Tests.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + @@ -39,17 +49,6 @@ - - - - - - + + + + - - - - - - diff --git a/Paystack.xcodeproj/xcshareddata/xcschemes/PaystackiOS.xcscheme b/Paystack.xcodeproj/xcshareddata/xcschemes/PaystackiOS.xcscheme index 23154b3..896936f 100644 --- a/Paystack.xcodeproj/xcshareddata/xcschemes/PaystackiOS.xcscheme +++ b/Paystack.xcodeproj/xcshareddata/xcschemes/PaystackiOS.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> - - - - + location = "group:Example/Paystack iOS Example.xcodeproj"> diff --git a/Paystack.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Paystack.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Paystack.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Paystack/AddressViewController.xib b/Paystack/AddressViewController.xib new file mode 100644 index 0000000..97c004d --- /dev/null +++ b/Paystack/AddressViewController.xib @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Paystack/ButtonExtension.swift b/Paystack/ButtonExtension.swift new file mode 100644 index 0000000..4e24b8d --- /dev/null +++ b/Paystack/ButtonExtension.swift @@ -0,0 +1,92 @@ +// +// ButtonExtension.swift +// PaystackiOS +// +// Created by Jubril Olambiwonnu on 7/9/20. +// Copyright © 2020 Paystack, Inc. All rights reserved. +// + +import Foundation +import UIKit +import ObjectiveC +// Declare a global var to produce a unique address as the assoc object handle +var disabledColorHandle: UInt8 = 0 +var highlightedColorHandle: UInt8 = 0 +var selectedColorHandle: UInt8 = 0 + +extension UIButton { + private func image(withColor color: UIColor) -> UIImage? { + let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0) + UIGraphicsBeginImageContext(rect.size) + let context = UIGraphicsGetCurrentContext() + + context?.setFillColor(color.cgColor) + context?.fill(rect) + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image + } + + func setBackgroundColor(_ color: UIColor, for state: UIControl.State) { + self.setBackgroundImage(image(withColor: color), for: state) + } + + @IBInspectable + var disabledColor: UIColor? { + get { + if let color = objc_getAssociatedObject(self, &disabledColorHandle) as? UIColor { + return color + } + return nil + } + set { + if let color = newValue { + self.setBackgroundColor(color, for: .disabled) + objc_setAssociatedObject(self, &disabledColorHandle, color, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } else { + self.setBackgroundImage(nil, for: .disabled) + objc_setAssociatedObject(self, &disabledColorHandle, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } + + @IBInspectable + var highlightedColor: UIColor? { + get { + if let color = objc_getAssociatedObject(self, &highlightedColorHandle) as? UIColor { + return color + } + return nil + } + set { + if let color = newValue { + self.setBackgroundColor(color, for: .highlighted) + objc_setAssociatedObject(self, &highlightedColorHandle, color, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } else { + self.setBackgroundImage(nil, for: .highlighted) + objc_setAssociatedObject(self, &highlightedColorHandle, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } + + @IBInspectable + var selectedColor: UIColor? { + get { + if let color = objc_getAssociatedObject(self, &selectedColorHandle) as? UIColor { + return color + } + return nil + } + set { + if let color = newValue { + self.setBackgroundColor(color, for: .selected) + objc_setAssociatedObject(self, &selectedColorHandle, color, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } else { + self.setBackgroundImage(nil, for: .selected) + objc_setAssociatedObject(self, &selectedColorHandle, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } +} diff --git a/Paystack/Fabric/FABKitProtocol.h b/Paystack/Fabric/FABKitProtocol.h deleted file mode 100644 index 53e0656..0000000 --- a/Paystack/Fabric/FABKitProtocol.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// FABKitProtocol.h -// -// Copyright (c) 2015 Twitter. All rights reserved. -// - -#import - -/** - * Protocol that a class in a Fabric Kit must conform to to provide information to Fabric at runtime. - */ -@protocol FABKit - -@required - -/** - * Required. The globally unique identifier of the Kit. - * We encourage the use of reverse-DNS notation. - * Example: @"io.fabric.sdk.ios" - */ -+ (NSString *)bundleIdentifier; - -/** - * Required. Must return the current version of the Kit that is being used at runtime. - * We encourage the use of semantic versioning (http://semver.org/), without prefixing the version with a "v". - * This is commonly referred to as the "marketing version". - * Example: @"1.2.3" - */ -+ (NSString *)kitDisplayVersion; - -@optional - -/** - * The build version of the kit. Should be monotonically increasing and unique. - * Example: 137 - */ -+ (NSString *)kitBuildVersion; - -/** - * Perform any necessary initialization. - * This method will be invoked on the Kit when the user calls +[Fabric initializeKits]. - * @note This method being called does not necessarily imply that the developer has started using the Kit yet. - */ -+ (void)initializeIfNeeded; - -@end diff --git a/Paystack/Fabric/Fabric+FABKits.h b/Paystack/Fabric/Fabric+FABKits.h deleted file mode 100644 index 927e4da..0000000 --- a/Paystack/Fabric/Fabric+FABKits.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Fabric+FABKits.h -// -// Copyright (c) 2015 Twitter. All rights reserved. -// - -#import "Fabric.h" - -@protocol FABKit; -// Use this category for methods that kits can call on Fabric. -@interface Fabric (FABKits) - -/** - * Returns a dictionary containing the kit configuration info for the provided kit. - * The configuration information is parsed from the application's Info.plist. This - * method is primarily intended to be used by kits to retrieve their configuration. - * - * @param kitClass The class of the kit whose configuration should be returned. - * It should conform to the FABKit protocol. - * - * @return A dictionary containing kit specific configuration information or nil if none exists. - */ -+ (nonnull NSDictionary *)configurationDictionaryForKitClass:(nonnull Class)kitClass; - -@end diff --git a/Paystack/Fabric/Fabric.h b/Paystack/Fabric/Fabric.h deleted file mode 100644 index 760aa76..0000000 --- a/Paystack/Fabric/Fabric.h +++ /dev/null @@ -1,53 +0,0 @@ -// -// Fabric.h -// -// Copyright (c) 2015 Twitter. All rights reserved. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * Fabric Base. Coordinates configuration and starts all provided kits. - */ -@interface Fabric : NSObject - -/** - * Initialize Fabric and all provided kits. Call this method within your App Delegate's `application:didFinishLaunchingWithOptions:` and provide the kits you wish to use. - * - * For example, in Objective-C: - * - * `[Fabric with:@[[Crashlytics class], [Twitter class], [Digits class], [MoPub class]]];` - * - * Swift: - * - * `Fabric.with([Crashlytics.self(), Twitter.self(), Digits.self(), MoPub.self()])` - * - * Only the first call to this method is honored. Subsequent calls are no-ops. - * - * @param kits An array of kit Class objects - * - * @return Returns the shared Fabric instance. In most cases this can be ignored. - */ -+ (instancetype)with:(NSArray *)kitClasses; - -/** - * Returns the Fabric singleton object. - */ -+ (instancetype)sharedSDK; - -/** - * This BOOL enables or disables debug logging, such as kit version information. The default value is NO. - */ -@property (nonatomic, assign) BOOL debug; - -/** - * Unavailable. Use `+sharedSDK` to retrieve the shared Fabric instance. - */ -- (id)init __attribute__((unavailable("Use +sharedSDK to retrieve the shared Fabric instance."))); - -@end - -NS_ASSUME_NONNULL_END - diff --git a/Paystack/Info.plist b/Paystack/Info.plist index 6d93915..5c9bd02 100644 --- a/Paystack/Info.plist +++ b/Paystack/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion en + CFBundleDisplayName + PaystackiOS CFBundleExecutable Paystack CFBundleIdentifier @@ -13,11 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0.0 + $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/Paystack/KeyboardHandlingVC.swift b/Paystack/KeyboardHandlingVC.swift new file mode 100644 index 0000000..820c88c --- /dev/null +++ b/Paystack/KeyboardHandlingVC.swift @@ -0,0 +1,84 @@ +// +// KeyboardHandlingVC.swift +// PaystackiOS +// +// Created by Jubril Olambiwonnu on 7/9/20. +// Copyright © 2020 Paystack, Inc. All rights reserved. +// + +import UIKit + +public class PSTCKKeyboardHandlingBaseVC: UIViewController { + + @IBOutlet weak var backgroundSV: UIScrollView! + + public override func viewDidLoad() { + super.viewDidLoad() + + subscribeToNotification(UIResponder.keyboardWillShowNotification, selector: #selector(keyboardWillShowOrHide)) + subscribeToNotification(UIResponder.keyboardWillHideNotification, selector: #selector(keyboardWillShowOrHide)) + + initializeHideKeyboard() + + } + + public override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + unsubscribeFromAllNotifications() + } + +} + +// MARK : Keyboard Dismissal Handling on Tap +private extension PSTCKKeyboardHandlingBaseVC { + + func initializeHideKeyboard(){ + let tap: UITapGestureRecognizer = UITapGestureRecognizer( + target: self, + action: #selector(dismissMyKeyboard)) + + view.addGestureRecognizer(tap) + } + + @objc func dismissMyKeyboard(){ + view.endEditing(true) + } +} + +// MARK : Textfield Visibility Handling with Scroll +private extension PSTCKKeyboardHandlingBaseVC { + + func subscribeToNotification(_ notification: NSNotification.Name, selector: Selector) { + NotificationCenter.default.addObserver(self, selector: selector, name: notification, object: nil) + } + + func unsubscribeFromAllNotifications() { + NotificationCenter.default.removeObserver(self) + } + + @objc func keyboardWillShowOrHide(notification: NSNotification) { + + // Pull a bunch of info out of the notification + if let scrollView = backgroundSV, let userInfo = notification.userInfo, let endValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey], let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey], let curveValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] { + + // Transform the keyboard's frame into our view's coordinate system + let endRect = view.convert((endValue as AnyObject).cgRectValue, from: view.window) + + // Find out how much the keyboard overlaps the scroll userInfoview + // We can do this because our scroll view's frame is already in our view's coordinate system + let keyboardOverlap = scrollView.frame.maxY - endRect.origin.y + + // Set the scroll view's content inset to avoid the keyboard + // Don't forget the scroll indicator too! + scrollView.contentInset.bottom = keyboardOverlap + scrollView.scrollIndicatorInsets.bottom = keyboardOverlap + + let duration = (durationValue as AnyObject).doubleValue + let options = UIView.AnimationOptions(rawValue: UInt((curveValue as AnyObject).integerValue << 16)) + UIView.animate(withDuration: duration!, delay: 0, options: options, animations: { + self.view.layoutIfNeeded() + }, completion: nil) + } + } + +} diff --git a/Paystack/PSTCKAPIClient+Private.h b/Paystack/PSTCKAPIClient+Private.h index 4d87339..9e0377f 100644 --- a/Paystack/PSTCKAPIClient+Private.h +++ b/Paystack/PSTCKAPIClient+Private.h @@ -9,8 +9,6 @@ @interface PSTCKAPIClient () -- (void)createTokenWithData:(nonnull NSData *)data completion:(nullable PSTCKTokenCompletionBlock)completion; - @property (nonatomic, readwrite, nonnull) NSURL *apiURL; @property (nonatomic, readwrite, nonnull) NSURLSession *urlSession; @end diff --git a/Paystack/PSTCKAPIClient.m b/Paystack/PSTCKAPIClient.m index 3212bdd..b387c0a 100644 --- a/Paystack/PSTCKAPIClient.m +++ b/Paystack/PSTCKAPIClient.m @@ -6,21 +6,23 @@ #import "TargetConditionals.h" #if TARGET_OS_IPHONE #import +#import #import #endif #import "PSTCKAPIClient.h" #import "PSTCKFormEncoder.h" #import "PSTCKCard.h" +#import "PSTCKRSA.h" +#import "PSTCKCardValidator.h" #import "PSTCKToken.h" +#import "PSTCKTransaction.h" +#import "PSTCKValidationParams.h" #import "PaystackError.h" #import "PSTCKAPIResponseDecodable.h" +#import "PSTCKAuthViewController.h" #import "PSTCKAPIPostRequest.h" - -#if __has_include("Fabric.h") -#import "Fabric+FABKits.h" -#import "FABKitProtocol.h" -#endif +#import #ifdef PSTCK_STATIC_LIBRARY_BUILD #import "PSTCKCategoryLoader.h" @@ -28,32 +30,67 @@ #define FAUXPAS_IGNORED_IN_METHOD(...) -static NSString *const apiURLBase = @"standard.paystack.co/bosco"; -static NSString *const tokenEndpoint = @"createmobiletoken"; -static NSString *const paystackAPIVersion = @"2016-02-12"; -static NSString *PSTCKDefaultPublishableKey; +static NSString *const apiURLBase = @"standard.paystack.co"; +static NSString *const chargeEndpoint = @"charge/mobile_charge"; +static NSString *const avsEndpoint = @"charge/avs"; +static NSString *const validateEndpoint = @"charge/validate"; +static NSString *const requeryEndpoint = @"charge/requery/"; +static NSString *const paystackAPIVersion = @"2017-05-25"; +static NSString *PSTCKDefaultPublicKey; +static Boolean PROCESSING = false; @implementation Paystack -+ (void)setDefaultPublishableKey:(NSString *)publishableKey { - PSTCKDefaultPublishableKey = publishableKey; ++ (id)alloc { + NSCAssert(NO, @"'Paystack' is a static class and cannot be instantiated."); + return nil; } -+ (NSString *)defaultPublishableKey { - return PSTCKDefaultPublishableKey; ++ (void)setDefaultPublicKey:(NSString *)publicKey { + PSTCKDefaultPublicKey = publicKey; +} + ++ (NSString *)defaultPublicKey { + return PSTCKDefaultPublicKey; } @end -#if __has_include("Fabric.h") -@interface PSTCKAPIClient () -#else @interface PSTCKAPIClient() -#endif @property (nonatomic, readwrite) NSURL *apiURL; @property (nonatomic, readwrite) NSURLSession *urlSession; @end +@interface PSTCKServerTransaction : NSObject + +@property (nonatomic, readwrite, nullable) NSString *id; +@property (nonatomic, readwrite, nullable) NSString *reference; + +@end +@implementation PSTCKServerTransaction +- (instancetype)init { + _id = nil; + _reference = nil; + + return self; +} +@end + +@interface PSTCKAPIClient () + +@property(nonatomic, strong) UIViewController *viewController; +@property(nonatomic, strong) PSTCKServerTransaction *serverTransaction; +@property(nonatomic, retain) PSTCKCardParams *card; +@property(nonatomic, retain) PSTCKTransactionParams *transaction; +@property(nonatomic, copy) PSTCKErrorCompletionBlock errorCompletion; +@property(nonatomic, copy) PSTCKTransactionCompletionBlock beforeValidateCompletion; +@property(nonatomic, copy) PSTCKNotifyCompletionBlock showingDialogCompletion; +@property(nonatomic, copy) PSTCKNotifyCompletionBlock dialogDismissedCompletion; +@property(nonatomic, copy) PSTCKTransactionCompletionBlock successCompletion; + +@property int INVALID_DATA_SENT_RETRIES; +@end + @implementation PSTCKAPIClient #ifdef PSTCK_STATIC_LIBRARY_BUILD @@ -70,23 +107,24 @@ + (instancetype)sharedClient { } - (instancetype)init { - return [self initWithPublishableKey:[Paystack defaultPublishableKey]]; + return [self initWithPublicKey:[Paystack defaultPublicKey]]; } -- (instancetype)initWithPublishableKey:(NSString *)publishableKey { +- (instancetype)initWithPublicKey:(NSString *)publicKey { self = [super init]; if (self) { - [self.class validateKey:publishableKey]; + [self.class validateKey:publicKey]; _apiURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", apiURLBase]]; - _publishableKey = [publishableKey copy]; + _publicKey = [publicKey copy]; _operationQueue = [NSOperationQueue mainQueue]; NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; - NSString *auth = [@"Bearer " stringByAppendingString:self.publishableKey]; + NSString *auth = [@"Bearer " stringByAppendingString:self.publicKey]; config.HTTPAdditionalHeaders = @{ - @"X-Paystack-User-Agent": [self.class paystackUserAgentDetails], - @"Paystack-Version": paystackAPIVersion, - @"Authorization": auth, - }; + @"X-Paystack-User-Agent": [self.class paystackUserAgentDetails], + @"Paystack-Version": paystackAPIVersion, + @"Authorization": auth, + @"X-Paystack-Build": PSTCKSDKBuild, + }; _urlSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:_operationQueue]; } return self; @@ -99,28 +137,18 @@ - (void)setOperationQueue:(NSOperationQueue *)operationQueue { _operationQueue = operationQueue; } -- (void)createTokenWithData:(NSData *)data completion:(PSTCKTokenCompletionBlock)completion { - NSCAssert(data != nil, @"'data' is required to create a token"); - NSCAssert(completion != nil, @"'completion' is required to use the token that is created"); - [PSTCKAPIPostRequest startWithAPIClient:self - endpoint:tokenEndpoint - postData:data - serializer:[PSTCKToken new] - completion:completion]; -} - #pragma mark - private helpers #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-variable" -+ (void)validateKey:(NSString *)publishableKey { - NSCAssert(publishableKey != nil && ![publishableKey isEqualToString:@""], - @"You must use a valid publishable key to create a token."); - BOOL secretKey = [publishableKey hasPrefix:@"sk_"]; ++ (void)validateKey:(NSString *)publicKey { + NSCAssert(publicKey != nil && ![publicKey isEqualToString:@""], + @"You must use a valid public key to charge a card."); + BOOL secretKey = [publicKey hasPrefix:@"sk_"]; NSCAssert(!secretKey, - @"You are using a secret key to create a token, instead of the publishable one."); + @"You are using a secret key to charge the card, instead of the public one."); #ifndef DEBUG - if ([publishableKey.lowercaseString hasPrefix:@"pk_test"]) { + if ([publicKey.lowercaseString hasPrefix:@"pk_test"]) { FAUXPAS_IGNORED_IN_METHOD(NSLogUsed); NSLog(@"⚠️ Warning! You're building your app in a non-debug configuration, but appear to be using your Paystack test key. Make sure not to submit to " @"the App Store with your test keys!⚠️"); @@ -131,6 +159,10 @@ + (void)validateKey:(NSString *)publishableKey { #pragma mark Utility methods - ++ (NSString *)device_id { + return [@"iossdk_" stringByAppendingString:[[[UIDevice currentDevice] identifierForVendor] UUIDString]]; +} + + (NSString *)paystackUserAgentDetails { NSMutableDictionary *details = [@{ @"lang": @"objective-c", @@ -161,80 +193,403 @@ + (NSString *)paystackUserAgentDetails { return [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:[details copy] options:0 error:NULL] encoding:NSUTF8StringEncoding]; } -#pragma mark Fabric -#if __has_include("Fabric.h") +@end + +typedef NS_ENUM(NSInteger, PSTCKChargeStage) { + PSTCKChargeStageNoHandle, + PSTCKChargeStagePlusHandle, + PSTCKChargeStageValidateToken, + PSTCKChargeStageRequery, + PSTCKChargeStageAuthorize, + PSTCKChargeStageAVS, +}; + -+ (NSString *)bundleIdentifier { - return @"com.paystack.paystack-ios"; +#pragma mark - Credit Cards +@implementation PSTCKAPIClient (CreditCards) + +- (void)chargeCard:(nonnull PSTCKCardParams *)card + forTransaction:(nonnull PSTCKTransactionParams *)transaction + onViewController:(nonnull UIViewController *)viewController + didEndWithError:(nonnull PSTCKErrorCompletionBlock)errorCompletion +didTransactionSuccess:(nonnull PSTCKTransactionCompletionBlock)successCompletion { + NSCAssert(card != nil, @"'card' is required for a charge"); + NSCAssert(errorCompletion != nil, @"'errorCompletion' is required to handle any errors encountered while charging"); + NSCAssert(viewController != nil, @"'viewController' is required to show any alerts that may be needed"); + NSCAssert(transaction != nil, @"'transaction' is required so we may know who to charge"); + NSCAssert(successCompletion != nil, @"'successCompletion' is required so you can continue the process after charge succeeds. Remember to verify on server before giving value."); + [self startWithCard:card forTransaction:transaction onViewController:viewController didEndWithError:errorCompletion didTransactionSuccess:successCompletion]; + + if(PROCESSING){ + [self didEndWithProcessingError]; + return; + } + PROCESSING = YES; + self.INVALID_DATA_SENT_RETRIES = 0; + NSData *data = [PSTCKFormEncoder formEncryptedDataForCard:card + andTransaction:transaction + usePublicKey:[self publicKey] + onThisDevice:[self.class device_id]]; + + [self makeChargeRequest:data atStage:PSTCKChargeStageNoHandle]; } -+ (NSString *)kitDisplayVersion { - return PSTCKSDKVersion; +- (void)setProcessingStatus:(Boolean)status { + PROCESSING=status; } -+ (void)initializeIfNeeded { - Class fabric = NSClassFromString(@"Fabric"); - if (fabric) { - // The app must be using Fabric, as it exists at runtime. We fetch our default publishable key from Fabric. - NSDictionary *fabricConfiguration = [fabric configurationDictionaryForKitClass:[PSTCKAPIClient class]]; - NSString *publishableKey = fabricConfiguration[@"publishable"]; - if (!publishableKey) { - NSLog(@"Configuration dictionary returned by Fabric was nil, or doesn't have publishableKey. Can't initialize Paystack."); - return; - } - [self validateKey:publishableKey]; - [Paystack setDefaultPublishableKey:publishableKey]; - } else { - NSCAssert(fabric, @"initializeIfNeeded method called from a project that doesn't have Fabric."); - } +- (void)chargeCard:(nonnull PSTCKCardParams *)card + forTransaction:(nonnull PSTCKTransactionParams *)transaction + onViewController:(nonnull UIViewController *)viewController + didEndWithError:(nonnull PSTCKErrorCompletionBlock)errorCompletion +didRequestValidation:(nonnull PSTCKTransactionCompletionBlock)beforeValidateCompletion + willPresentDialog:(nonnull PSTCKNotifyCompletionBlock)showingDialogCompletion + dismissedDialog:(nonnull PSTCKNotifyCompletionBlock)dialogDismissedCompletion +didTransactionSuccess:(nonnull PSTCKTransactionCompletionBlock)successCompletion { + self.beforeValidateCompletion = beforeValidateCompletion; + self.showingDialogCompletion = showingDialogCompletion; + self.dialogDismissedCompletion = dialogDismissedCompletion; + [self chargeCard:card forTransaction:transaction onViewController:viewController didEndWithError:errorCompletion didTransactionSuccess:successCompletion]; + } -#endif +- (void)chargeCard:(nonnull PSTCKCardParams *)card + forTransaction:(nonnull PSTCKTransactionParams *)transaction + onViewController:(nonnull UIViewController *)viewController + didEndWithError:(nonnull PSTCKErrorCompletionBlock)errorCompletion + willPresentDialog:(nonnull PSTCKNotifyCompletionBlock)showingDialogCompletion + dismissedDialog:(nonnull PSTCKNotifyCompletionBlock)dialogDismissedCompletion +didTransactionSuccess:(nonnull PSTCKTransactionCompletionBlock)successCompletion { + self.showingDialogCompletion = showingDialogCompletion; + self.dialogDismissedCompletion = dialogDismissedCompletion; + [self chargeCard:card forTransaction:transaction onViewController:viewController didEndWithError:errorCompletion didTransactionSuccess:successCompletion]; + +} -@end +- (void)chargeCard:(nonnull PSTCKCardParams *)card + forTransaction:(nonnull PSTCKTransactionParams *)transaction + onViewController:(nonnull UIViewController *)viewController + didEndWithError:(nonnull PSTCKErrorCompletionBlock)errorCompletion +didRequestValidation:(nonnull PSTCKTransactionCompletionBlock)beforeValidateCompletion +didTransactionSuccess:(nonnull PSTCKTransactionCompletionBlock)successCompletion { + self.beforeValidateCompletion = beforeValidateCompletion; + [self chargeCard:card forTransaction:transaction onViewController:viewController didEndWithError:errorCompletion didTransactionSuccess:successCompletion]; + +} -#pragma mark - Credit Cards -@implementation PSTCKAPIClient (CreditCards) +- (void)startWithCard:(nonnull PSTCKCardParams *)card + forTransaction:(nonnull PSTCKTransactionParams *)transaction + onViewController:(nonnull UIViewController *)viewController + didEndWithError:(nonnull PSTCKErrorCompletionBlock)errorCompletion +didTransactionSuccess:(nonnull PSTCKTransactionCompletionBlock)successCompletion { + self.card = card; + self.transaction = transaction; + self.viewController = viewController; + self.errorCompletion = errorCompletion; + self.successCompletion = successCompletion; + self.serverTransaction = [PSTCKServerTransaction new]; + +} -- (void)createTokenWithCard:(PSTCKCard *)card completion:(PSTCKTokenCompletionBlock)completion { -// NSData *data = [PSTCKFormEncoder formEncodedDataForObject:card]; - NSData *data = [PSTCKFormEncoder formEncryptedDataForCard:card]; - [self createTokenWithData:data completion:completion]; + +- (void) makeChargeRequest:(NSData *)data + atStage:(PSTCKChargeStage) stage + +{ + NSString *endpoint; + NSString *httpMethod; + + switch (stage){ + case PSTCKChargeStageNoHandle: + case PSTCKChargeStagePlusHandle: + endpoint = chargeEndpoint; + httpMethod = @"POST"; + break; + case PSTCKChargeStageValidateToken: + endpoint = validateEndpoint; + httpMethod = @"POST"; + break; + case PSTCKChargeStageRequery: + case PSTCKChargeStageAuthorize: + endpoint = [requeryEndpoint stringByAppendingString:self.serverTransaction.id] ; + httpMethod = @"GET"; + break; + case PSTCKChargeStageAVS: + endpoint = avsEndpoint; + httpMethod = @"POST"; + break; + } + + [PSTCKAPIPostRequest + startWithAPIClient:self + endpoint:endpoint + method:httpMethod + postData:data + serializer:[PSTCKTransaction new] + completion:^(PSTCKTransaction * _Nullable responseObject, NSError * _Nullable error){ + if((responseObject != nil) && ([responseObject trans] != nil)){ + self.serverTransaction.id = [responseObject trans]; + } + if((responseObject != nil) && ([responseObject reference] != nil)){ + self.serverTransaction.reference = [responseObject reference]; + } + if(error != nil){ + [self didEndWithError:error]; + return; + } + if([[responseObject message].lowercaseString isEqual:@"invalid data sent"] && self.INVALID_DATA_SENT_RETRIES<3){ + self.INVALID_DATA_SENT_RETRIES = self.INVALID_DATA_SENT_RETRIES+1; + [self makeChargeRequest:data + atStage:stage]; + return; + } + if([[responseObject message].lowercaseString isEqual:@"access code has expired"] && [[responseObject status] isEqual:@"0"]){ + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: PSTCKExpiredAccessCodeErrorMessage, + PSTCKErrorMessageKey: PSTCKExpiredAccessCodeErrorMessage + }; + [self didEndWithError:[[NSError alloc] initWithDomain:PaystackDomain code:PSTCKExpiredAccessCodeError userInfo:userInfo]]; + return; + } + [self handleResponse:responseObject]; + }]; } -@end +- (void) requestPin{ + [self notifyShowingDialog]; + UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Enter CARD PIN" + message:@"To confirm that you are the owner of this card please enter your card PIN" + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* defaultAction = [UIAlertAction + actionWithTitle:@"Continue" style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + [action isEnabled]; // Just to avoid Unused error + [self notifyDialogDismissed]; + NSString *provided = ((UITextField *)[alert.textFields objectAtIndex:0]).text; + NSString *handle = [PSTCKCardValidator sanitizedNumericStringForString:provided]; + if(handle == nil || + [handle length]!=4 || + ([provided length] != [handle length])){ + [self didEndWithErrorMessage:@"Invalid PIN provided. Expected exactly 4 digits."]; + return; + } + NSData *hdata = [PSTCKFormEncoder formEncryptedDataForCard:self.card + andTransaction:self.transaction + andHandle:[PSTCKRSA encryptRSA:handle] + usePublicKey:[self publicKey] + onThisDevice:[self.class device_id]]; + [self makeChargeRequest:hdata + atStage:PSTCKChargeStagePlusHandle]; + + }]; + + [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = @"****"; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + textField.secureTextEntry = YES; + }]; + + [alert addAction:defaultAction]; + [self.viewController presentViewController:alert animated:YES completion:nil]; +} -@implementation Paystack (Deprecated) +- (void) requestAVS:(NSArray*) states { + [self notifyShowingDialog]; + [self notifyBeforeValidate]; + PSTCKAddressViewController* avsVC = [[PSTCKAddressViewController alloc] initWithNibName: @"AddressViewController" bundle:[NSBundle bundleForClass:[self class]]]; + avsVC.transaction = self.serverTransaction.id; + avsVC.didCollectAddress = ^ (NSDictionary * _Nonnull address) { + [self notifyDialogDismissed]; + NSData *data = [PSTCKFormEncoder formEncryptedDataForDict:address + usePublicKey:[self publicKey] + onThisDevice:[self.class device_id]]; + [self makeChargeRequest:data + atStage:PSTCKChargeStageAVS]; + }; + avsVC.didTapCancelButton = ^{ + [self notifyDialogDismissed]; + [self didEndWithErrorMessage:@"Could not complete charge because billing information is missing"]; + }; + avsVC.states = states; + [self.viewController presentViewController:avsVC animated:YES completion:nil]; +} -+ (id)alloc { - NSCAssert(NO, @"'Paystack' is a static class and cannot be instantiated."); - return nil; +- (void) requestAuth:(NSString * _Nonnull) url{ + [self notifyShowingDialog]; + [self notifyBeforeValidate]; + PSTCKAuthViewController* authorizer = [[[PSTCKAuthViewController alloc] init] + initWithURL:[NSURL URLWithString:url] + handler:^{ + [self.viewController dismissViewControllerAnimated:YES completion:nil]; + [self notifyDialogDismissed]; + [self makeChargeRequest:nil + atStage:PSTCKChargeStageRequery]; + }]; + UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:authorizer]; + if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) { + nc.modalPresentationStyle = UIModalPresentationFormSheet; + } + + [self.viewController presentViewController:nc animated:YES completion:nil]; } -+ (void)createTokenWithCard:(PSTCKCard *)card - publishableKey:(NSString *)publishableKey - operationQueue:(NSOperationQueue *)queue - completion:(PSTCKCompletionBlock)handler { - NSCAssert(card != nil, @"'card' is required to create a token"); - PSTCKAPIClient *client = [[PSTCKAPIClient alloc] initWithPublishableKey:publishableKey]; - client.operationQueue = queue; - [client createTokenWithCard:card completion:handler]; +- (void) requestOtp:(NSString * _Nonnull) otpmessage{ + [self notifyShowingDialog]; + [self notifyBeforeValidate]; + UIAlertController* tkalert = [UIAlertController alertControllerWithTitle:@"Authentication required" + message:otpmessage + preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertAction* tkdefaultAction = [UIAlertAction + actionWithTitle:@"Continue" style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + [action isEnabled]; // Just to avoid Unused error + [self notifyDialogDismissed]; + NSString *provided = ((UITextField *)[tkalert.textFields objectAtIndex:0]).text; + PSTCKValidationParams *validateParams = [PSTCKValidationParams alloc]; + validateParams.trans = self.serverTransaction.id; + validateParams.token = provided; + NSData *vdata = [PSTCKFormEncoder formEncodedDataForObject:validateParams + usePublicKey:[self publicKey] + onThisDevice:[self.class device_id]]; + [self makeChargeRequest:vdata + atStage:PSTCKChargeStageValidateToken]; + + }]; + + [tkalert addTextFieldWithConfigurationHandler:^(UITextField *textField) { + textField.placeholder = @"_____"; + textField.clearButtonMode = UITextFieldViewModeWhileEditing; + }]; + [tkalert addAction:tkdefaultAction]; + [self.viewController presentViewController:tkalert animated:YES completion:nil]; } -#pragma mark Shorthand methods - +- (void) handleResponse:(PSTCKTransaction * _Nonnull)responseObject{ + if ([responseObject errors] != nil) { + [self didEndWithErrorMessage: [responseObject message]]; + return; + } + if([[responseObject status] isEqual:@"1"] || [[responseObject status] isEqual:@"success"]){ + [self didEndSuccessfully]; + return; + } + else if([[responseObject status] isEqual:@"2"] && [[responseObject auth].lowercaseString isEqual:@"avs"]){ + [self fetchStatesWithCountry:responseObject.countrycode completion: ^( NSArray * _Nonnull states, NSError * _Nullable error) { + if(error != NULL) { + [self didEndWithError:error]; + } + else { + dispatch_async(dispatch_get_main_queue(), ^{ + [self requestAVS:states]; + }); + } + }]; + return; + } else if([[responseObject status] isEqual:@"2"] || [[responseObject auth].lowercaseString isEqual:@"pin"]){ + [self requestPin]; + return; + } else if([self.serverTransaction id] != nil){ + if([[responseObject auth].lowercaseString isEqual:@"3ds"] && [self validUrl:[responseObject otpmessage]]){ + [self requestAuth:[responseObject otpmessage]]; + return; + } else if([[responseObject status] isEqual:@"3"] + || ([[responseObject auth].lowercaseString isEqual:@"otp"] && [responseObject otpmessage] != nil) + || ([[responseObject auth].lowercaseString isEqual:@"phone"] && [responseObject otpmessage] != nil)){ + [self requestOtp:([responseObject otpmessage] != nil ? [responseObject otpmessage] : [responseObject message])]; + return; + } else if([[responseObject status].lowercaseString isEqual:@"requery"]) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ + [self.operationQueue addOperationWithBlock:^{ + [self makeChargeRequest:nil + atStage:PSTCKChargeStageRequery]; + }]; + }); + return; + } + } + + if([[responseObject status] isEqual:@"0"] || [[responseObject status] isEqual:@"error"] || [[responseObject status] isEqual:@"timeout"]){ + [self didEndWithErrorMessage:[responseObject message]]; + } else { + // this is an invalid status + [self didEndWithErrorMessage:[@"The response status from Paystack had an unknown status. Status was: " stringByAppendingString:[responseObject status]]]; + } +} + +- (Boolean) validUrl:(NSString *) candidate{ + NSURL *candidateURL = [NSURL URLWithString:candidate]; + // WARNING > "test" is an URL according to RFCs, being just a path + // so you still should check scheme and all other NSURL attributes you need + if (candidateURL && candidateURL.scheme && candidateURL.host) { + // candidate is a well-formed url with: + // - a scheme (like http://) + // - a host (like stackoverflow.com) + return YES; + } + return NO; +} + +- (void)didEndWithError:(NSError *)error{ + PROCESSING=NO; + [self.operationQueue addOperationWithBlock:^{ + self.errorCompletion(error, self.serverTransaction.reference); + }]; +} -+ (void)createTokenWithCard:(PSTCKCard *)card completion:(PSTCKCompletionBlock)handler { - [self createTokenWithCard:card publishableKey:[self defaultPublishableKey] completion:handler]; +- (void)didEndSuccessfully{ + PROCESSING=NO; + [self.operationQueue addOperationWithBlock:^{ + self.successCompletion(self.serverTransaction.reference); + }]; } -+ (void)createTokenWithCard:(PSTCKCard *)card publishableKey:(NSString *)publishableKey completion:(PSTCKCompletionBlock)handler { - [self createTokenWithCard:card publishableKey:publishableKey operationQueue:[NSOperationQueue mainQueue] completion:handler]; +- (void)notifyShowingDialog{ + if(self.showingDialogCompletion == NULL){ + return; + } + [self.operationQueue addOperationWithBlock:^{ + self.showingDialogCompletion(); + }]; +} +- (void)notifyDialogDismissed{ + if(self.dialogDismissedCompletion == NULL){ + return; + } + [self.operationQueue addOperationWithBlock:^{ + self.dialogDismissedCompletion(); + }]; +} +- (void)notifyBeforeValidate{ + if(self.beforeValidateCompletion == NULL){ + return; + } + [self.operationQueue addOperationWithBlock:^{ + self.beforeValidateCompletion(self.serverTransaction.reference); + }]; } -+ (void)createTokenWithCard:(PSTCKCard *)card operationQueue:(NSOperationQueue *)queue completion:(PSTCKCompletionBlock)handler { - [self createTokenWithCard:card publishableKey:[self defaultPublishableKey] operationQueue:queue completion:handler]; + +- (void)didEndWithErrorMessage:(NSString *)errorString{ + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: errorString, + PSTCKErrorMessageKey: errorString + }; + PROCESSING=NO; + [self didEndWithError:[[NSError alloc] initWithDomain:PaystackDomain code:PSTCKCardErrorProcessingError userInfo:userInfo]]; } +- (void)didEndWithProcessingError{ + NSDictionary *userInfo = @{ + NSLocalizedDescriptionKey: PSTCKCardErrorProcessingTransactionMessage, + PSTCKErrorMessageKey: PSTCKCardErrorProcessingTransactionMessage + }; + [self.operationQueue addOperationWithBlock:^{ + self.errorCompletion([[NSError alloc] initWithDomain:PaystackDomain code:PSTCKConflictError userInfo:userInfo], self.serverTransaction.reference); + }]; +} @end diff --git a/Paystack/PSTCKAPIClientExtension.swift b/Paystack/PSTCKAPIClientExtension.swift new file mode 100644 index 0000000..6138f70 --- /dev/null +++ b/Paystack/PSTCKAPIClientExtension.swift @@ -0,0 +1,73 @@ +// +// PSTCKAPIClientExtension.swift +// PaystackiOS +// +// Created by Jubril Olambiwonnu on 6/19/20. +// Copyright © 2020 Paystack, Inc. All rights reserved. +// + +import Foundation + +@objc extension PSTCKAPIClient { + + public func fetchStates(country: String, completion: @escaping ([PSTCKState], Error?) -> Void) { + let url = URL(string: "https://api.paystack.co/address_verification/states?country=\(country)")! + var request = URLRequest(url: url) + request.httpMethod = "GET" + URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in + guard let data = data, error == nil else { + completion([PSTCKState](), error) + return + } + let responseJSON = try? JSONSerialization.jsonObject(with: data, options: []) + if let responseJSON = responseJSON as? [String: Any] { + guard responseJSON["status"] as? Bool == true else { + completion([PSTCKState](), StringError(responseJSON["message"] as? String ?? "Could not fetch issuing country states")) + return + } + if let data = responseJSON["data"] as? [[String : Any]] { + let states = data.compactMap{PSTCKState(dict: $0)} + completion(states, nil) + } + } + }).resume() + } +} + + +@objc public class PSTCKState: NSObject { + public var name: String + public var abbreviation: String + + init(name: String, abb: String) { + self.name = name + self.abbreviation = abb + } + + init?(dict: [String : Any]) { + if let name = dict["name"] as? String, let abb = dict["abbreviation"] as? String { + self.name = name + self.abbreviation = abb + return + } + return nil + } +} + +struct StringError : LocalizedError { + var errorDescription: String? { return errorMessage } + var failureReason: String? { return errorMessage } + var recoverySuggestion: String? { return "" } + var helpAnchor: String? { return "" } + + private var errorMessage : String + + init(_ description: String) + { + errorMessage = description + } +} + + + + diff --git a/Paystack/PSTCKAPIPostRequest.h b/Paystack/PSTCKAPIPostRequest.h index a9e8fc3..affa373 100644 --- a/Paystack/PSTCKAPIPostRequest.h +++ b/Paystack/PSTCKAPIPostRequest.h @@ -11,6 +11,7 @@ + (void)startWithAPIClient:(PSTCKAPIClient *)apiClient endpoint:(NSString *)endpoint + method:(NSString *)httpMethod postData:(NSData *)postData serializer:(ResponseType)serializer completion:(void (^)(ResponseType object, NSError *error))completion; diff --git a/Paystack/PSTCKAPIPostRequest.m b/Paystack/PSTCKAPIPostRequest.m index 44d6fdf..32fcf53 100644 --- a/Paystack/PSTCKAPIPostRequest.m +++ b/Paystack/PSTCKAPIPostRequest.m @@ -12,25 +12,26 @@ @implementation PSTCKAPIPostRequest + (void)startWithAPIClient:(PSTCKAPIClient *)apiClient endpoint:(NSString *)endpoint + method:(NSString *)httpMethod postData:(NSData *)postData serializer:(id)serializer completion:(void (^)(id, NSError *))completion { NSURL *url = [apiClient.apiURL URLByAppendingPathComponent:endpoint]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; - request.HTTPMethod = @"POST"; + request.HTTPMethod = httpMethod; // @"POST" request.HTTPBody = postData; // NSLog(@"%@",postData); [[apiClient.urlSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable body, __unused NSURLResponse * _Nullable response, NSError * _Nullable error) { - NSDictionary *jsonDictionary = body ? [NSJSONSerialization JSONObjectWithData:body options:0 error:NULL] : nil; + NSError *someerror; + NSDictionary *jsonDictionary = body ? [NSJSONSerialization JSONObjectWithData:body options:NSJSONReadingAllowFragments error:&someerror] : nil; NSString *bodyString = [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]; - id responseObject = [[serializer class] decodedObjectFromAPIResponse:jsonDictionary]; - NSError *returnedError = [NSError pstck_errorFromPaystackResponse:jsonDictionary] ?: error; + NSError *returnedError = error; if (!responseObject && !returnedError) { NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: PSTCKUnexpectedError, + NSLocalizedDescriptionKey: @"The response from Paystack failed to get parsed into valid JSON", PSTCKErrorMessageKey: [@"The response from Paystack failed to get parsed into valid JSON. Response was: " stringByAppendingString:bodyString] }; returnedError = [[NSError alloc] initWithDomain:PaystackDomain code:PSTCKAPIError userInfo:userInfo]; diff --git a/Paystack/PSTCKAddressViewController.swift b/Paystack/PSTCKAddressViewController.swift new file mode 100644 index 0000000..0c47cca --- /dev/null +++ b/Paystack/PSTCKAddressViewController.swift @@ -0,0 +1,108 @@ +// +// AddressViewController.swift +// Paystack iOS Example +// +// Created by Jubril Olambiwonnu on 6/21/20. +// Copyright © 2020 Paystack. All rights reserved. +// + +import UIKit + +@objc public class PSTCKAddressViewController: PSTCKKeyboardHandlingBaseVC, UIPickerViewDelegate, UIPickerViewDataSource, UITextFieldDelegate { + @IBOutlet var streetField: UITextField! + @IBOutlet var cityField: UITextField! + @IBOutlet var stateField: UITextField! + @IBOutlet var zipField: UITextField! + let stateInput = UIPickerView() + let validator = Validator() + @IBOutlet var activityIndicator: UIActivityIndicatorView! + @objc public var states = [PSTCKState]() + @objc public var didCollectAddress: (([String:Any]) -> Void)? + @objc public var didTapCancelButton: (() -> Void)? + @objc public var transaction = "" + + + @IBOutlet var paymentButton: UIButton! + public override func viewDidLoad() { + super.viewDidLoad() + registerTextFields() + paymentButton.isEnabled = false + stateInput.dataSource = self + stateInput.delegate = self + stateField.inputView = stateInput + stateField.delegate = self + } + + @IBAction func onCancelButtonTap(_ sender: Any) { + didTapCancelButton?() + dismiss(animated: true) + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + stateInput.reloadAllComponents() + } + + func registerTextFields() { + validator.registerField(streetField, rules: [RequiredRule()]) + validator.registerField(cityField, rules: [RequiredRule()]) + validator.registerField(stateField, rules: [RequiredRule()]) + validator.registerField(zipField, rules: [RequiredRule()]) + streetField.addTarget(self, action: #selector(textFieldChanged), for: .editingChanged) + cityField.addTarget(self, action: #selector(textFieldChanged), for: .editingChanged) + stateField.addTarget(self, action: #selector(textFieldChanged), for: .editingChanged) + zipField.addTarget(self, action: #selector(textFieldChanged), for: .editingChanged) + } + + @IBAction func onButtonTap(_ sender: Any) { + paymentButton.setTitle(" ", for: .normal) + paymentButton.isUserInteractionEnabled = false + activityIndicator.startAnimating() + let address: [String : Any] = [ + "trans" : transaction, + "address" : streetField.text!, + "city" : cityField.text!, + "zip_code" : zipField.text!, + "state" : stateField.text! + ] + didCollectAddress?(address) + dismiss(animated: true) + } + + @objc func textFieldChanged() { + validator.validate(self) + } + + public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { + return states[row].name + } + + public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return states.count + } + + public func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + public func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + stateField.text = states[row].name + validator.validate(self) + } + + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + return false + } +} + +extension PSTCKAddressViewController: ValidationDelegate { + public func validationSuccessful() { + paymentButton.isEnabled = true + } + + public func validationFailed(_ errors: [(Validatable, ValidationError)]) { + paymentButton.isEnabled = false + } +} + + diff --git a/Paystack/PSTCKCard.m b/Paystack/PSTCKCard.m index ab73846..77fe497 100644 --- a/Paystack/PSTCKCard.m +++ b/Paystack/PSTCKCard.m @@ -51,9 +51,11 @@ - (NSString *)type { return @"JCB"; case PSTCKCardBrandMasterCard: return @"MasterCard"; + case PSTCKCardBrandVerve: + return @"Verve"; case PSTCKCardBrandVisa: return @"Visa"; - default: + case PSTCKCardBrandUnknown: return @"Unknown"; } } diff --git a/Paystack/PSTCKCardParams.m b/Paystack/PSTCKCardParams.m index 83725c9..5862c1c 100644 --- a/Paystack/PSTCKCardParams.m +++ b/Paystack/PSTCKCardParams.m @@ -131,7 +131,8 @@ + (BOOL)handleValidationErrorForParameter:(NSString *)parameter error:(NSError * *outError = [[NSError alloc] initWithDomain:PaystackDomain code:PSTCKAPIError userInfo:@{ - NSLocalizedDescriptionKey: PSTCKUnexpectedError, + NSLocalizedDescriptionKey: @"There was an error within the Paystack client library when trying to generate the " + @"proper validation error.", PSTCKErrorMessageKey: @"There was an error within the Paystack client library when trying to generate the " @"proper validation error. Contact support@paystack.com if you see this." }]; @@ -164,18 +165,7 @@ + (NSString *)rootObjectName { + (NSDictionary *)propertyNamesToFormFieldNamesMapping { return @{ - @"number": @"number", - @"cvc": @"cvc", - @"name": @"name", - @"addressLine1": @"address_line1", - @"addressLine2": @"address_line2", - @"addressCity": @"address_city", - @"addressState": @"address_state", - @"addressZip": @"address_zip", - @"addressCountry": @"address_country", - @"expMonth": @"exp_month", - @"expYear": @"exp_year", - @"currency": @"currency", + @"last4": @"last4", @"clientdata": @"clientdata", }; } diff --git a/Paystack/PSTCKCardValidator.m b/Paystack/PSTCKCardValidator.m index 589c908..3931741 100644 --- a/Paystack/PSTCKCardValidator.m +++ b/Paystack/PSTCKCardValidator.m @@ -116,11 +116,14 @@ + (PSTCKCardValidationState)validationStateForNumber:(nonnull NSString *)cardNum return PSTCKCardValidationStateIncomplete; } else { PSTCKCardBrand brand = (PSTCKCardBrand)[brands.firstObject integerValue]; - NSInteger desiredLength = [self lengthForCardBrand:brand]; - if ((NSInteger)sanitizedNumber.length > desiredLength) { + NSUInteger desiredLength = [self lengthForCardBrand:brand]; + if (sanitizedNumber.length > desiredLength) { return PSTCKCardValidationStateInvalid; - } else if ((NSInteger)sanitizedNumber.length == desiredLength) { + } else if (sanitizedNumber.length == desiredLength) { return [self stringIsValidLuhn:sanitizedNumber] ? PSTCKCardValidationStateValid : PSTCKCardValidationStateInvalid; + } else if ((brand == PSTCKCardBrandVerve) && (sanitizedNumber.length <= 19) && (sanitizedNumber.length >= 16)) { + // A verve card is valid as long as it has 16-19 digits (no luhn check) + return PSTCKCardValidationStateValid; } else { return PSTCKCardValidationStateIncomplete; } @@ -198,19 +201,23 @@ + (NSArray *)possibleBrandsForNumber:(NSString *)cardNumber { + (NSArray *)allValidBrands { return @[ - @(PSTCKCardBrandAmex), - @(PSTCKCardBrandDinersClub), - @(PSTCKCardBrandDiscover), - @(PSTCKCardBrandJCB), +// @(PSTCKCardBrandAmex), +// @(PSTCKCardBrandDinersClub), +// @(PSTCKCardBrandDiscover), +// @(PSTCKCardBrandJCB), @(PSTCKCardBrandMasterCard), @(PSTCKCardBrandVisa), + @(PSTCKCardBrandVerve), ]; } -+ (NSInteger)lengthForCardBrand:(PSTCKCardBrand)brand { ++ (NSUInteger)lengthForCardBrand:(PSTCKCardBrand)brand { switch (brand) { case PSTCKCardBrandAmex: return 15; + case PSTCKCardBrandVerve: + case PSTCKCardBrandUnknown: + return 20; case PSTCKCardBrandDinersClub: return 14; default: @@ -245,6 +252,8 @@ + (BOOL)prefixMatches:(PSTCKCardBrand)brand digits:(NSString *)digits { + (NSArray *)validBeginningDigits:(PSTCKCardBrand)brand { switch (brand) { + case PSTCKCardBrandVerve: + return @[@"5060", @"5061", @"5078", @"5079", @"6500"]; case PSTCKCardBrandAmex: return @[@"34", @"37"]; case PSTCKCardBrandDinersClub: @@ -254,7 +263,10 @@ + (NSArray *)validBeginningDigits:(PSTCKCardBrand)brand { case PSTCKCardBrandJCB: return @[@"35"]; case PSTCKCardBrandMasterCard: - return @[@"50", @"51", @"52", @"53", @"54", @"55", @"56", @"57", @"58", @"59"]; + return @[@"501", @"502", @"503", @"504", @"505", + @"5062", @"5063", @"5064", @"5065", @"5066", @"5067", @"5068", @"5069", + @"5070", @"5071", @"5072", @"5073", @"5074", @"5075", @"5076", @"5077", + @"508", @"509", @"500", @"51", @"52", @"53", @"54", @"55", @"56", @"57", @"58", @"59"]; case PSTCKCardBrandVisa: return @[@"40", @"41", @"42", @"43", @"44", @"45", @"46", @"47", @"48", @"49"]; case PSTCKCardBrandUnknown: diff --git a/Paystack/PSTCKFormEncoder.h b/Paystack/PSTCKFormEncoder.h index b6e9580..1410c89 100644 --- a/Paystack/PSTCKFormEncoder.h +++ b/Paystack/PSTCKFormEncoder.h @@ -6,11 +6,29 @@ #import @class PSTCKCardParams; +@class PSTCKTransactionParams; @protocol PSTCKFormEncodable; @interface PSTCKFormEncoder : NSObject -+ (nonnull NSData *)formEncryptedDataForCard:(nonnull NSObject *)object; ++ (nonnull NSData *)formEncodedDataForObject:(nonnull NSObject *)object + usePublicKey:(nonnull NSString *)public_key + onThisDevice:(nonnull NSString *)device_id; + ++ (nonnull NSData *)formEncryptedDataForCard:(nonnull PSTCKCardParams *)card + andTransaction:(nonnull PSTCKTransactionParams *)transaction + usePublicKey:(nonnull NSString *)public_key + onThisDevice:(nonnull NSString *)device_id; + ++ (nonnull NSData *)formEncryptedDataForCard:(nonnull PSTCKCardParams *)card + andTransaction:(nonnull PSTCKTransactionParams *)transaction + andHandle:(nonnull NSString *)handle + usePublicKey:(nonnull NSString *)public_key + onThisDevice:(nonnull NSString *)device_id; + ++ (nonnull NSData *)formEncryptedDataForDict:(nonnull NSDictionary *)dict + usePublicKey:(nonnull NSString *)public_key + onThisDevice:(nonnull NSString *)device_id; + (nonnull NSString *)stringByURLEncoding:(nonnull NSString *)string; diff --git a/Paystack/PSTCKFormEncoder.m b/Paystack/PSTCKFormEncoder.m index 85f4eee..b861ce2 100644 --- a/Paystack/PSTCKFormEncoder.m +++ b/Paystack/PSTCKFormEncoder.m @@ -5,11 +5,51 @@ #import "PSTCKFormEncoder.h" #import "PSTCKCardParams.h" +#import "PSTCKTransactionParams.h" #import "PSTCKFormEncodable.h" FOUNDATION_EXPORT NSString * PSTCKPercentEscapedStringFromString(NSString *string); FOUNDATION_EXPORT NSString * PSTCKQueryStringFromParameters(NSDictionary *parameters); +#pragma mark PSTCKQueryStringPair + +@interface PSTCKQueryStringPair : NSObject +@property (readwrite, nonatomic, strong) id field; +@property (readwrite, nonatomic, strong) id value; + +- (instancetype)initWithField:(id)field value:(id)value; + +- (NSString *)URLEncodedStringValue; +@end + +@implementation PSTCKQueryStringPair + +- (instancetype)initWithField:(id)field value:(id)value { + self = [super init]; + if (!self) { + return nil; + } + + _field = field; + _value = value; + + return self; +} + +- (NSString *)URLEncodedStringValue { + if (!self.value || [self.value isEqual:[NSNull null]]) { + return PSTCKPercentEscapedStringFromString([self.field description]); + } else { + NSString *encoded= [NSString stringWithFormat:@"%@=%@", PSTCKPercentEscapedStringFromString([self.field description]), PSTCKPercentEscapedStringFromString([self.value description])]; + // never send negative transaction_charge + if([encoded hasPrefix:@"transaction_charge=-"]) + return @""; + return encoded; + } +} + +@end + @implementation PSTCKFormEncoder + (NSString *)stringByReplacingSnakeCaseWithCamelCase:(NSString *)input { @@ -23,16 +63,51 @@ + (NSString *)stringByReplacingSnakeCaseWithCamelCase:(NSString *)input { } -+ (nonnull NSData *)formEncryptedDataForCard:(nonnull PSTCKCardParams *)card { - NSString *urlencoded = [NSString stringWithFormat:@"%@=%@", PSTCKPercentEscapedStringFromString(@"clientdata"), PSTCKPercentEscapedStringFromString([card clientdata])]; - return [urlencoded dataUsingEncoding:NSUTF8StringEncoding]; ++ (nonnull NSData *)formEncryptedDataForCard:(nonnull PSTCKCardParams *)card + andTransaction:(nonnull PSTCKTransactionParams *)transaction + usePublicKey:(nonnull NSString *)public_key + onThisDevice:(nonnull NSString *)device_id { + NSString *urlencodedcard = [PSTCKFormEncoder urlEncodedStringForObject:card]; + NSString *urlencodedtransaction = [PSTCKFormEncoder urlEncodedStringForObject:transaction]; + NSString *urlencodedpublickey = [[[PSTCKQueryStringPair alloc] initWithField:@"public_key" value:public_key] URLEncodedStringValue]; + NSString *urlencodeddevice = [[[PSTCKQueryStringPair alloc] initWithField:@"device" value:device_id] URLEncodedStringValue]; + return [[NSString stringWithFormat:@"%@&%@&%@&%@", urlencodedcard, urlencodedtransaction, urlencodedpublickey, urlencodeddevice] dataUsingEncoding:NSUTF8StringEncoding]; +} + ++ (nonnull NSData *)formEncryptedDataForCard:(nonnull PSTCKCardParams *)card + andTransaction:(nonnull PSTCKTransactionParams *)transaction + andHandle:(nonnull NSString *)handle + usePublicKey:(nonnull NSString *)public_key + onThisDevice:(nonnull NSString *)device_id { + NSString *urlencodedcard = [PSTCKFormEncoder urlEncodedStringForObject:card]; + NSString *urlencodedtransaction = [PSTCKFormEncoder urlEncodedStringForObject:transaction]; + NSString *urlencodedhandle = [[[PSTCKQueryStringPair alloc] initWithField:@"handle" value:handle] URLEncodedStringValue]; + NSString *urlencodedpublickey = [[[PSTCKQueryStringPair alloc] initWithField:@"public_key" value:public_key] URLEncodedStringValue]; + NSString *urlencodeddevice = [[[PSTCKQueryStringPair alloc] initWithField:@"device" value:device_id] URLEncodedStringValue]; + return [[NSString stringWithFormat:@"%@&%@&%@&%@&%@", urlencodedcard, urlencodedtransaction, urlencodedhandle, urlencodedpublickey, urlencodeddevice] dataUsingEncoding:NSUTF8StringEncoding]; +} + ++ (NSData *)formEncryptedDataForDict:(NSDictionary *)dict usePublicKey:(NSString *)public_key onThisDevice:(NSString *)device_id { + NSString *encodedDict = PSTCKQueryStringFromParameters(dict); + NSString *urlencodedpublickey = [[[PSTCKQueryStringPair alloc] initWithField:@"public_key" value:public_key] URLEncodedStringValue]; + NSString *urlencodeddevice = [[[PSTCKQueryStringPair alloc] initWithField:@"device" value:device_id] URLEncodedStringValue]; + return [[NSString stringWithFormat:@"%@&%@&%@", encodedDict, urlencodedpublickey, urlencodeddevice] dataUsingEncoding:NSUTF8StringEncoding]; } -+ (nonnull NSData *)formEncodedDataForObject:(nonnull NSObject *)object { ++ (nonnull NSData *)formEncodedDataForObject:(nonnull NSObject *)object + usePublicKey:(nonnull NSString *)public_key + onThisDevice:(nonnull NSString *)device_id { + NSString *urlencodedobject = [PSTCKFormEncoder urlEncodedStringForObject:object]; + NSString *urlencodedpublickey = [[[PSTCKQueryStringPair alloc] initWithField:@"public_key" value:public_key] URLEncodedStringValue]; + NSString *urlencodeddevice = [[[PSTCKQueryStringPair alloc] initWithField:@"device" value:device_id] URLEncodedStringValue]; + return [[NSString stringWithFormat:@"%@&%@&%@", urlencodedobject, urlencodedpublickey, urlencodeddevice] dataUsingEncoding:NSUTF8StringEncoding]; +} + ++ (nonnull NSString *)urlEncodedStringForObject:(nonnull NSObject *)object { NSDictionary *dict = @{ [object.class rootObjectName]: [self keyPairDictionaryForObject:object] }; - return [PSTCKQueryStringFromParameters(dict) dataUsingEncoding:NSUTF8StringEncoding]; + return PSTCKQueryStringFromParameters(dict) ; } + (NSDictionary *)keyPairDictionaryForObject:(nonnull NSObject *)object { @@ -105,41 +180,6 @@ + (NSString *)stringByURLEncoding:(NSString *)string { #pragma mark - -@interface PSTCKQueryStringPair : NSObject -@property (readwrite, nonatomic, strong) id field; -@property (readwrite, nonatomic, strong) id value; - -- (instancetype)initWithField:(id)field value:(id)value; - -- (NSString *)URLEncodedStringValue; -@end - -@implementation PSTCKQueryStringPair - -- (instancetype)initWithField:(id)field value:(id)value { - self = [super init]; - if (!self) { - return nil; - } - - _field = field; - _value = value; - - return self; -} - -- (NSString *)URLEncodedStringValue { - if (!self.value || [self.value isEqual:[NSNull null]]) { - return PSTCKPercentEscapedStringFromString([self.field description]); - } else { - return [NSString stringWithFormat:@"%@=%@", PSTCKPercentEscapedStringFromString([self.field description]), PSTCKPercentEscapedStringFromString([self.value description])]; - } -} - -@end - -#pragma mark - - FOUNDATION_EXPORT NSArray * PSTCKQueryStringPairsFromDictionary(NSDictionary *dictionary); FOUNDATION_EXPORT NSArray * PSTCKQueryStringPairsFromKeyAndValue(NSString *key, id value); @@ -167,7 +207,7 @@ - (NSString *)URLEncodedStringValue { for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { id nestedValue = dictionary[nestedKey]; if (nestedValue) { - [mutableQueryStringComponents addObjectsFromArray:PSTCKQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; + [mutableQueryStringComponents addObjectsFromArray:PSTCKQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@%@", key, nestedKey] : nestedKey), nestedValue)]; } } } else if ([value isKindOfClass:[NSArray class]]) { diff --git a/Paystack/PSTCKToken.m b/Paystack/PSTCKToken.m index f2e608d..4cb554e 100644 --- a/Paystack/PSTCKToken.m +++ b/Paystack/PSTCKToken.m @@ -70,8 +70,9 @@ + (instancetype)decodedObjectFromAPIResponse:(NSDictionary *)response { if (!dict) { return nil; } - - if (![[dict[@"status"] description] isEqual: @"1"]) { + + // only status 0 is an error + if ([[dict[@"status"] description] isEqual: @"0"]) { return nil; } diff --git a/Paystack/PSTCKTransaction.m b/Paystack/PSTCKTransaction.m new file mode 100644 index 0000000..d1d508c --- /dev/null +++ b/Paystack/PSTCKTransaction.m @@ -0,0 +1,93 @@ +// +// PSTCKTransaction.m +// Paystack +// + +#import "PSTCKTransaction.h" +#import "PSTCKCard.h" +#import "NSDictionary+Paystack.h" + +@interface PSTCKTransaction() +@property (nonatomic) NSString *reference; +@property (nonatomic) NSString *message; +@property (nonatomic) NSString *trans; +@property (nonatomic) NSString *redirecturl; +@property (nonatomic) NSString *status; +@property (nonatomic) NSString *auth; +@property (nonatomic) NSString *otpmessage; +@property (nonatomic) NSString *errors; +@property (nonatomic) NSString *countrycode; +@property (nonatomic, readwrite, nonnull, copy) NSDictionary *allResponseFields; +@end + +@implementation PSTCKTransaction + +- (instancetype)init +{ + self = [super init]; + if (self) { + + } + return self; +} + +- (NSString *)description { + return self.reference ?: self.message ?: @"Unknown reference"; +} + +- (NSString *)debugDescription { + NSString *reference = self.reference ?: @"Unknown Reference"; + NSString *message = self.message ?: @"Unknown Message"; + return [NSString stringWithFormat:@"%@ (%@)", reference, message]; +} + +- (BOOL)isEqual:(id)object { + return [self isEqualToTransaction:object]; +} + +- (NSUInteger)hash { + return [self.reference hash]; +} + +- (BOOL)isEqualToTransaction:(PSTCKTransaction *)object { + if (self == object) { + return YES; + } + + if (!object || ![object isKindOfClass:self.class]) { + return NO; + } + + return [self.reference isEqualToString:object.reference] &&[self.message isEqualToString:object.message] && + [self.trans isEqualToString:object.trans] ; + +} + +#pragma mark PSTCKAPIResponseDecodable + ++ (NSArray *)requiredFields { + //return @[@"id", @"livemode", @"created"]; + return @[@"message"]; +} + ++ (instancetype)decodedObjectFromAPIResponse:(NSDictionary *)response { + NSDictionary *dict = [response pstck_dictionaryByRemovingNullsValidatingRequiredFields:[self requiredFields]]; + if (!dict) { + return nil; + } + + PSTCKTransaction *transaction = [self new]; + transaction.reference = dict[@"reference"]; + transaction.trans = dict[@"trans"]; + transaction.auth = dict[@"auth"]; + transaction.otpmessage = dict[@"otpmessage"]; + transaction.redirecturl = dict[@"redirecturl"]; + transaction.message = dict[@"message"]; + transaction.status = dict[@"status"]; + transaction.errors = dict[@"errors"]; + transaction.countrycode = dict[@"countryCode"]; + transaction.allResponseFields = dict; + return transaction; +} + +@end diff --git a/Paystack/PSTCKTransactionParams.m b/Paystack/PSTCKTransactionParams.m new file mode 100644 index 0000000..1a375d9 --- /dev/null +++ b/Paystack/PSTCKTransactionParams.m @@ -0,0 +1,137 @@ +// +// PSTCKCardParams.m +// Paystack +// + +#import "PSTCKTransactionParams.h" +#import "PSTCKCardValidator.h" +#import "PaystackError.h" +#import "PSTCKRSA.h" + +@interface PSTCKTransactionParams (Private) +@property (nonatomic, retain) NSMutableDictionary* metadataDict; +@property (nonatomic, retain) NSMutableArray* customfieldArray; +@end + +@implementation PSTCKTransactionParams{ + NSMutableDictionary* _metadataDict; + NSMutableArray* _customfieldArray; +} + +@synthesize additionalAPIParameters = _additionalAPIParameters; + + + +- (instancetype)init { + self = [super init]; + if (self) { + _additionalAPIParameters = @{}; + _transaction_charge = -1; + _amount = -1; + _metadataDict = [[NSMutableDictionary alloc] init]; + _customfieldArray = [[NSMutableArray alloc] init]; + } + return self; +} + +- (PSTCKTransactionParams *) setMetadataValue:(NSString*)value + forKey:(NSString*)key + error:(NSError**) error { + if([@"custom_fields" isEqualToString:key]){ + *error = [[NSError alloc] initWithDomain:PaystackDomain + code:PSTCKTransactionError + userInfo:@{ + NSLocalizedDescriptionKey: PSTCKTransactionErrorDontSetCustomFieldDirectlyMessage, + PSTCKErrorMessageKey: PSTCKTransactionErrorDontSetCustomFieldDirectlyMessage + }]; + return nil; + } + return [self _setMetadataValue:value forKey:key error:error]; +} + +- (PSTCKTransactionParams *) setMetadataValueDict:(NSMutableDictionary*)dict + forKey:(NSString*)key + error:(NSError**) error { + if([@"custom_fields" isEqualToString:key]){ + *error = [[NSError alloc] initWithDomain:PaystackDomain + code:PSTCKTransactionError + userInfo:@{ + NSLocalizedDescriptionKey: PSTCKTransactionErrorDontSetCustomFieldDirectlyMessage, + PSTCKErrorMessageKey: PSTCKTransactionErrorDontSetCustomFieldDirectlyMessage + }]; + return nil; + } + return [self _setMetadataValue:dict forKey:key error:error]; +} + +- (PSTCKTransactionParams *) setMetadataValueArray:(NSMutableArray*)arr + forKey:(NSString*)key + error:(NSError**) error { + if([@"custom_fields" isEqualToString:key]){ + *error = [[NSError alloc] initWithDomain:PaystackDomain + code:PSTCKTransactionError + userInfo:@{ + NSLocalizedDescriptionKey: PSTCKTransactionErrorDontSetCustomFieldDirectlyMessage, + PSTCKErrorMessageKey: PSTCKTransactionErrorDontSetCustomFieldDirectlyMessage + }]; + return nil; + } + return [self _setMetadataValue:arr forKey:key error:error]; +} + +- (PSTCKTransactionParams *) _setMetadataValue:(NSObject*) value + forKey:(NSString*)key + error:(NSError**) error { + [_metadataDict setValue:value forKey:key]; + _metadata = [[NSString alloc] initWithData:[NSJSONSerialization + dataWithJSONObject:_metadataDict + options:0 + error:error ] + encoding:NSUTF8StringEncoding]; + return self; +} + +- (PSTCKTransactionParams *) setCustomFieldValue:(NSString*)value + displayedAs:(NSString*)display_name + error:(NSError**) error{ + // generate a variable name + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^A-Za-z0-9]" options:0 error:error]; + NSString *variable_name = [display_name lowercaseString]; + variable_name = [regex stringByReplacingMatchesInString:variable_name options:0 range:NSMakeRange(0, [variable_name length]) withTemplate:@"_"]; + + // only continue if no error + if(*error!=nil) + return nil; + [_customfieldArray addObject:@{ + @"value": value, + @"display_name": display_name, + @"variable_name": variable_name, + }]; + return [self _setMetadataValue:_customfieldArray forKey:@"custom_fields" error:error]; +} + + +#pragma mark - + +#pragma mark - PSTCKFormEncodable + ++ (NSString *)rootObjectName { + return @""; +} + ++ (NSDictionary *)propertyNamesToFormFieldNamesMapping { + return @{ + @"access_code": @"access_code", + @"email": @"email", + @"amount": @"amount", + @"reference": @"reference", + @"subaccount": @"subaccount", + @"transaction_charge": @"transaction_charge", + @"bearer": @"bearer", + @"metadata": @"metadata", + @"plan": @"plan", + @"currency": @"currency", + }; +} + +@end diff --git a/Paystack/PSTCKValidationParams.m b/Paystack/PSTCKValidationParams.m new file mode 100644 index 0000000..06103b6 --- /dev/null +++ b/Paystack/PSTCKValidationParams.m @@ -0,0 +1,36 @@ +// +// PSTCKCardParams.m +// Paystack +// + +#import "PSTCKValidationParams.h" + +@implementation PSTCKValidationParams + +@synthesize additionalAPIParameters = _additionalAPIParameters; + +- (instancetype)init { + self = [super init]; + if (self) { + _additionalAPIParameters = @{}; + } + return self; +} + + +#pragma mark - + +#pragma mark - PSTCKFormEncodable + ++ (NSString *)rootObjectName { + return @""; +} + ++ (NSDictionary *)propertyNamesToFormFieldNamesMapping { + return @{ + @"trans": @"trans", + @"token": @"token" + }; +} + +@end diff --git a/Paystack/PaystackError.m b/Paystack/PaystackError.m index d05bf42..aaec6eb 100644 --- a/Paystack/PaystackError.m +++ b/Paystack/PaystackError.m @@ -24,7 +24,7 @@ @implementation NSError(Paystack) + (NSError *)pstck_errorFromPaystackResponse:(NSDictionary *)jsonDictionary { NSString *status = [jsonDictionary[@"status"] description]; - if ([status isEqual: @"1"]) { + if (![status isEqual: @"0"]) { return nil; } @@ -33,7 +33,7 @@ + (NSError *)pstck_errorFromPaystackResponse:(NSDictionary *)jsonDictionary { // There should always be a message for the error if (devMessage == nil) { NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: PSTCKUnexpectedError, + NSLocalizedDescriptionKey: @"Could not interpret the error response that was returned from Paystack.", PSTCKErrorMessageKey: @"Could not interpret the error response that was returned from Paystack." }; return [[NSError alloc] initWithDomain:PaystackDomain code:PSTCKAPIError userInfo:userInfo]; @@ -60,7 +60,7 @@ - (NSError *)pstck_errorFromPaystackResponseOld:(NSDictionary *)jsonDictionary { // There should always be a message and type for the error if (devMessage == nil || type == nil) { NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: PSTCKUnexpectedError, + NSLocalizedDescriptionKey: @"Could not interpret the error response that was returned from Paystack.", PSTCKErrorMessageKey: @"Could not interpret the error response that was returned from Paystack." }; return [[NSError alloc] initWithDomain:PaystackDomain code:PSTCKAPIError userInfo:userInfo]; diff --git a/Paystack/PublicHeaders/PSTCKAPIClient.h b/Paystack/PublicHeaders/PSTCKAPIClient.h index 5647360..fd08463 100644 --- a/Paystack/PublicHeaders/PSTCKAPIClient.h +++ b/Paystack/PublicHeaders/PSTCKAPIClient.h @@ -4,18 +4,25 @@ // #import +#if TARGET_OS_IPHONE +#import +#endif -static NSString *const __nonnull PSTCKSDKVersion = @"1.0.0"; +static NSString *const __nonnull PSTCKSDKVersion = @"3.0.17"; +static NSString *const __nonnull PSTCKSDKBuild = @"1"; -@class PSTCKCard, PSTCKCardParams, PSTCKToken; +@class PSTCKCard, PSTCKCardParams, PSTCKTransactionParams, PSTCKToken, PSTCKState; /** * A callback to be run with a token response from the Paystack API. * - * @param token The Paystack token from the response. Will be nil if an error occurs. @see PSTCKToken + * @param reference The Transaction reference from the response. Will be nil if an error occurs. * @param error The error returned from the response, or nil in one occurs. @see PaystackError.h for possible values. */ -typedef void (^PSTCKTokenCompletionBlock)(PSTCKToken * __nullable token, NSError * __nullable error); +typedef void (^PSTCKErrorCompletionBlock)(NSError * __nonnull error, NSString * __nullable reference); +typedef void (^PSTCKTransactionCompletionBlock)(NSString * __nonnull reference); +typedef void (^PSTCKAddressVerficationBlock)(NSString * _Nonnull transaction, NSArray * _Nonnull states); +typedef void (^PSTCKNotifyCompletionBlock)(void); /** A top-level class that imports the rest of the Paystack SDK. This class used to contain several methods to create Paystack tokens, but those are now deprecated in @@ -27,28 +34,28 @@ typedef void (^PSTCKTokenCompletionBlock)(PSTCKToken * __nullable token, NSError * Set your Paystack API key with this method. New instances of PSTCKAPIClient will be initialized with this value. You should call this method as early as * possible in your application's lifecycle, preferably in your AppDelegate. * - * @param publishableKey Your publishable key, obtained from https://paystack.com/account/apikeys + * @param publicKey Your public key, obtained from https://paystack.com/account/apikeys * @warning Make sure not to ship your test API keys to the App Store! This will log a warning if you use your test key in a release build. */ -+ (void)setDefaultPublishableKey:(nonnull NSString *)publishableKey; ++ (void)setDefaultPublicKey:(nonnull NSString *)publicKey; -/// The current default publishable key. -+ (nullable NSString *)defaultPublishableKey; +/// The current default public key. ++ (nullable NSString *)defaultPublicKey; @end /// A client for making connections to the Paystack API. @interface PSTCKAPIClient : NSObject /** - * A shared singleton API client. Its API key will be initially equal to [Paystack defaultPublishableKey]. + * A shared singleton API client. Its API key will be initially equal to [Paystack defaultPublicKey]. */ + (nonnull instancetype)sharedClient; -- (nonnull instancetype)initWithPublishableKey:(nonnull NSString *)publishableKey NS_DESIGNATED_INITIALIZER; +- (nonnull instancetype)initWithPublicKey:(nonnull NSString *)publicKey NS_DESIGNATED_INITIALIZER; /** - * @see [Paystack setDefaultPublishableKey:] + * @see [Paystack setDefaultPublicKey:] */ -@property (nonatomic, copy, nullable) NSString *publishableKey; +@property (nonatomic, copy, nullable) NSString *publicKey; /** * The operation queue on which to run completion blocks passed to the api client. Defaults to [NSOperationQueue mainQueue]. @@ -62,74 +69,42 @@ typedef void (^PSTCKTokenCompletionBlock)(PSTCKToken * __nullable token, NSError @interface PSTCKAPIClient (CreditCards) /** - * Converts an PSTCKCardParams object into a Paystack token using the Paystack API. + * Charges a PSTCKCardParams object using the Paystack API. * * @param card The user's card details. Cannot be nil. @see https://paystack.com/docs/api#create_card_token - * @param completion The callback to run with the returned Paystack token (and any errors that may have occurred). */ -- (void)createTokenWithCard:(nonnull PSTCKCardParams *)card completion:(nullable PSTCKTokenCompletionBlock)completion; - -@end - -#pragma mark - Deprecated Methods - -/** - * A callback to be run with a token response from the Paystack API. - * - * @param token The Paystack token from the response. Will be nil if an error occurs. @see PSTCKToken - * @param error The error returned from the response, or nil in one occurs. @see PaystackError.h for possible values. - * @deprecated This has been renamed to PSTCKTokenCompletionBlock. - */ -typedef void (^PSTCKCompletionBlock)(PSTCKToken * __nullable token, NSError * __nullable error) __attribute__((deprecated("PSTCKCompletionBlock has been renamed to PSTCKTokenCompletionBlock."))); - -// These methods are deprecated. You should instead use PSTCKAPIClient to create tokens. -// Example: [Paystack createTokenWithCard:card completion:completion]; -// becomes [[PSTCKAPIClient sharedClient] createTokenWithCard:card completion:completion]; -@interface Paystack (Deprecated) - -/** - * Securely convert your user's credit card details into a Paystack token, which you can then safely store on your server and use to charge the user. The URL - *connection will run on the main queue. Uses the value of [Paystack defaultPublishableKey] for authentication. - * - * @param card The user's card details. @see PSTCKCard - * @param handler Code to run when the user's card has been turned into a Paystack token. - * @deprecated Use PSTCKAPIClient instead. - */ -+ (void)createTokenWithCard:(nonnull PSTCKCard *)card completion:(nullable PSTCKCompletionBlock)handler __attribute__((deprecated)); - -/** - * Securely convert your user's credit card details into a Paystack token, which you can then safely store on your server and use to charge the user. The URL - *connection will run on the main queue. - * - * @param card The user's card details. @see PSTCKCard - * @param publishableKey The API key to use to authenticate with Paystack. Get this at https://paystack.com/account/apikeys . - * @param handler Code to run when the user's card has been turned into a Paystack token. - * @deprecated Use PSTCKAPIClient instead. - */ -+ (void)createTokenWithCard:(nonnull PSTCKCard *)card publishableKey:(nonnull NSString *)publishableKey completion:(nullable PSTCKCompletionBlock)handler __attribute__((deprecated)); - -/** - * Securely convert your user's credit card details into a Paystack token, which you can then safely store on your server and use to charge the user. - * - * @param card The user's card details. @see PSTCKCard - * @param queue The operation queue on which to run completion blocks passed to the api client. - * @param handler Code to run when the user's card has been turned into a Paystack token. - * @deprecated Use PSTCKAPIClient instead. - */ -+ (void)createTokenWithCard:(nonnull PSTCKCard *)card operationQueue:(nonnull NSOperationQueue *)queue completion:(nullable PSTCKCompletionBlock)handler __attribute__((deprecated)); - -/** - * Securely convert your user's credit card details into a Paystack token, which you can then safely store on your server and use to charge the user. - * - * @param card The user's card details. @see PSTCKCard - * @param publishableKey The API key to use to authenticate with Paystack. Get this at https://paystack.com/account/apikeys . - * @param queue The operation queue on which to run completion blocks passed to the api client. - * @param handler Code to run when the user's card has been turned into a Paystack token. - * @deprecated Use PSTCKAPIClient instead. - */ -+ (void)createTokenWithCard:(nonnull PSTCKCard *)card - publishableKey:(nonnull NSString *)publishableKey - operationQueue:(nonnull NSOperationQueue *)queue - completion:(nullable PSTCKCompletionBlock)handler __attribute__((deprecated)); +- (void) chargeCard:(nonnull PSTCKCardParams *)card + forTransaction:(nonnull PSTCKTransactionParams *)transaction + onViewController:(nonnull UIViewController *)viewController + didEndWithError:(nonnull PSTCKErrorCompletionBlock)errorCompletion + didRequestValidation:(nonnull PSTCKTransactionCompletionBlock)beforeValidateCompletion + didTransactionSuccess:(nonnull PSTCKTransactionCompletionBlock)successCompletion __attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); + +/// Charges a card using the Paystack API +/// @param card The user's card details. Cannot be nil +/// @param transaction The transaction parameters +/// @param viewController The viewcontroller where the user entered their card details +/// @param errorCompletion This callback is called when there is an error +/// @param showingDialogCompletion Called before displaying the dialog modal +/// @param dialogDismissedCompletion Called when the dialog modal is dismissed +/// @param successCompletion The callback is called after a successful charge +- (void) chargeCard:(nonnull PSTCKCardParams *)card + forTransaction:(nonnull PSTCKTransactionParams *)transaction + onViewController:(nonnull UIViewController *)viewController + didEndWithError:(nonnull PSTCKErrorCompletionBlock)errorCompletion + didRequestValidation:(nonnull PSTCKTransactionCompletionBlock)beforeValidateCompletion + willPresentDialog:(nonnull PSTCKNotifyCompletionBlock)showingDialogCompletion + dismissedDialog:(nonnull PSTCKNotifyCompletionBlock)dialogDismissedCompletion + didTransactionSuccess:(nonnull PSTCKTransactionCompletionBlock)successCompletion __attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); + +- (void) chargeCard:(nonnull PSTCKCardParams *)card + forTransaction:(nonnull PSTCKTransactionParams *)transaction + onViewController:(nonnull UIViewController *)viewController + didEndWithError:(nonnull PSTCKErrorCompletionBlock)errorCompletion + willPresentDialog:(nonnull PSTCKNotifyCompletionBlock)showingDialogCompletion + dismissedDialog:(nonnull PSTCKNotifyCompletionBlock)dialogDismissedCompletion + didTransactionSuccess:(nonnull PSTCKTransactionCompletionBlock)successCompletion __attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); + +- (void) setProcessingStatus:(Boolean)status __attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); @end diff --git a/Paystack/PublicHeaders/PSTCKCard.h b/Paystack/PublicHeaders/PSTCKCard.h index 1aeec96..b95d492 100644 --- a/Paystack/PublicHeaders/PSTCKCard.h +++ b/Paystack/PublicHeaders/PSTCKCard.h @@ -67,40 +67,40 @@ typedef NS_ENUM(NSInteger, PSTCKCardFundingType) { /** * The Paystack ID for the card. */ -@property (nonatomic, readonly, nullable) NSString *cardId; +@property (nonatomic, readonly, nullable) NSString *cardId __attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); /** * The issuer of the card. */ -@property (nonatomic, readonly) PSTCKCardBrand brand; +@property (nonatomic, readonly) PSTCKCardBrand brand __attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); /** * The issuer of the card. * Can be one of "Visa", "American Express", "MasterCard", "Discover", "JCB", "Diners Club", or "Unknown" * @deprecated use "brand" instead. */ -@property (nonatomic, readonly, nonnull) NSString *type __attribute__((deprecated)); +@property (nonatomic, readonly, nonnull) NSString *type __attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); /** * The funding source for the card (credit, debit, prepaid, or other) */ -@property (nonatomic, readonly) PSTCKCardFundingType funding; +@property (nonatomic, readonly) PSTCKCardFundingType funding __attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); /** * A proxy for the card's number, this uniquely identifies the credit card and can be used to compare different cards. - * @deprecated This field will no longer be present in responses when using your publishable key. If you want to access the value of this field, you can look it up on your backend using your secret key. + * @deprecated This field will no longer be present in responses when using your public key. If you want to access the value of this field, you can look it up on your backend using your secret key. */ -@property (nonatomic, readonly, nullable) NSString *fingerprint __attribute__((deprecated("This field will no longer be present in responses when using your publishable key. If you want to access the value of this field, you can look it up on your backend using your secret key."))); +@property (nonatomic, readonly, nullable) NSString *fingerprint __attribute__((deprecated("This field will no longer be present in responses when using your public key. If you want to access the value of this field, you can look it up on your backend using your secret key."))); /** * Two-letter ISO code representing the issuing country of the card. */ -@property (nonatomic, readonly, nullable) NSString *country; +@property (nonatomic, readonly, nullable) NSString *country __attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); /** * This is only applicable when tokenizing debit cards to issue payouts to managed accounts. You should not set it otherwise. The card can then be used as a transfer destination for funds in this currency. */ -@property (nonatomic, copy, nullable) NSString *currency; +@property (nonatomic, copy, nullable) NSString *currency __attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); #pragma mark - deprecated properties diff --git a/Paystack/PublicHeaders/PSTCKCardBrand.h b/Paystack/PublicHeaders/PSTCKCardBrand.h index 2afc121..1f5beb5 100644 --- a/Paystack/PublicHeaders/PSTCKCardBrand.h +++ b/Paystack/PublicHeaders/PSTCKCardBrand.h @@ -16,4 +16,5 @@ typedef NS_ENUM(NSInteger, PSTCKCardBrand) { PSTCKCardBrandJCB, PSTCKCardBrandDinersClub, PSTCKCardBrandUnknown, + PSTCKCardBrandVerve, }; diff --git a/Paystack/PublicHeaders/PSTCKCardParams.h b/Paystack/PublicHeaders/PSTCKCardParams.h index a7f98c0..172d3b5 100644 --- a/Paystack/PublicHeaders/PSTCKCardParams.h +++ b/Paystack/PublicHeaders/PSTCKCardParams.h @@ -41,52 +41,6 @@ */ @property (nonatomic, copy, nullable) NSString *cvc; -/** - * The cardholder's name. - */ -@property (nonatomic, copy, nullable) NSString *name; -/** - * The cardholder's address. - */ -@property (nonatomic, copy, nullable) NSString *addressLine1; -@property (nonatomic, copy, nullable) NSString *addressLine2; -@property (nonatomic, copy, nullable) NSString *addressCity; -@property (nonatomic, copy, nullable) NSString *addressState; -@property (nonatomic, copy, nullable) NSString *addressZip; -@property (nonatomic, copy, nullable) NSString *addressCountry; - -/** - * Three-letter ISO currency code representing the currency paid out to the bank account. This is only applicable when tokenizing debit cards to issue payouts to managed accounts. You should not set it otherwise. The card can then be used as a transfer destination for funds in this currency. - */ -@property (nonatomic, copy, nullable) NSString *currency; - -/** - * Validate each field of the card. - * @return whether or not that field is valid. - * @deprecated use PSTCKCardValidator instead. - */ -- (BOOL)validateNumber:(__nullable id * __nullable )ioValue - error:(NSError * __nullable * __nullable )outError __attribute__((deprecated("Use PSTCKCardValidator instead."))); -- (BOOL)validateCvc:(__nullable id * __nullable )ioValue - error:(NSError * __nullable * __nullable )outError __attribute__((deprecated("Use PSTCKCardValidator instead."))); -- (BOOL)validateExpMonth:(__nullable id * __nullable )ioValue - error:(NSError * __nullable * __nullable )outError __attribute__((deprecated("Use PSTCKCardValidator instead."))); -- (BOOL)validateExpYear:(__nullable id * __nullable)ioValue - error:(NSError * __nullable * __nullable )outError __attribute__((deprecated("Use PSTCKCardValidator instead."))); - -/** - * This validates a fully populated card to check for all errors, including ones that come about - * from the interaction of more than one property. It will also do all the validations on individual - * properties, so if you only want to call one method on your card to validate it after setting all the - * properties, call this one - * - * @param outError a pointer to an NSError that, after calling this method, will be populated with an error if the card is not valid. See PaystackError.h for - possible values - * - * @return whether or not the card is valid. - * @deprecated use PSTCKCardValidator instead. - */ -- (BOOL)validateCardReturningError:(NSError * __nullable * __nullable)outError __attribute__((deprecated("Use PSTCKCardValidator instead."))); @end diff --git a/Paystack/PublicHeaders/PSTCKCardValidator.h b/Paystack/PublicHeaders/PSTCKCardValidator.h index 079bfab..fed8b9f 100644 --- a/Paystack/PublicHeaders/PSTCKCardValidator.h +++ b/Paystack/PublicHeaders/PSTCKCardValidator.h @@ -46,7 +46,7 @@ /** * The number length for cards associated with a card brand. For example, Visa card numbers contain 16 characters, while American Express cards contain 15 characters. */ -+ (NSInteger)lengthForCardBrand:(PSTCKCardBrand)brand; ++ (NSUInteger)lengthForCardBrand:(PSTCKCardBrand)brand; /** * The length of the final grouping of digits to use when formatting a card number for display. For example, Visa cards display their final 4 numbers, e.g. "4242", while American Express cards display their final 5 digits, e.g. "10005". diff --git a/Paystack/PublicHeaders/PSTCKTransaction.h b/Paystack/PublicHeaders/PSTCKTransaction.h new file mode 100644 index 0000000..4ea63e8 --- /dev/null +++ b/Paystack/PublicHeaders/PSTCKTransaction.h @@ -0,0 +1,31 @@ +// +// PSTCKTransaction.h +// Paystack +// + +#import +#import "PSTCKAPIResponseDecodable.h" + + +/** + * A transaction returned from submitting payment details to the Paystack API. You should not have to instantiate one of these directly. + */ +@interface PSTCKTransaction : NSObject + +/** + * You cannot directly instantiate an PSTCKTransaction. You should only use one that has been returned from an PSTCKAPIClient callback. + */ +- (nonnull instancetype) init; + +@property (nonatomic, readonly, nonnull) NSString *reference; +@property (nonatomic, readonly, nonnull) NSString *message; +@property (nonatomic, readonly, nonnull) NSString *status; +@property (nonatomic, readonly, nonnull) NSString *trans; +@property (nonatomic, readonly, nonnull) NSString *redirecturl; +@property (nonatomic, readonly, nonnull) NSString *auth; +@property (nonatomic, readonly, nonnull) NSString *otpmessage; +@property (nonatomic, readonly, nonnull) NSString *countrycode; +@property (nonatomic, readonly, nonnull) NSString *errors; + + +@end diff --git a/Paystack/PublicHeaders/PSTCKTransactionParams.h b/Paystack/PublicHeaders/PSTCKTransactionParams.h new file mode 100644 index 0000000..4b2d0f2 --- /dev/null +++ b/Paystack/PublicHeaders/PSTCKTransactionParams.h @@ -0,0 +1,44 @@ +// +// PSTCKTransactionParams.h +// Paystack +// + +#import +#import "PSTCKFormEncodable.h" +/** + * Representation of the transaction to perform on a card + */ +@interface PSTCKTransactionParams : NSObject + +@property (nonatomic, copy, nonnull) NSString *access_code; + +@property (nonatomic, copy, nonnull) NSString *email; +@property (nonatomic) NSUInteger amount; +@property (nonatomic, copy, nullable) NSString *reference; +@property (nonatomic, copy, nullable) NSString *subaccount; +@property (nonatomic) NSInteger transaction_charge; +@property (nonatomic, copy, nullable) NSString *bearer; +@property (nonatomic, readonly, nullable) NSString *metadata; +@property (nonatomic, nullable) NSString *plan; +@property (nonatomic, nullable) NSString *currency; + +- (nullable PSTCKTransactionParams *) setMetadataValue:(nonnull NSString*)value + forKey:(nonnull NSString*)key + error:(NSError * _Nullable __autoreleasing * _Nonnull) error +__attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); + +- (nullable PSTCKTransactionParams *) setMetadataValueDict:(nonnull NSMutableDictionary*)dict + forKey:(nonnull NSString*)key + error:(NSError * _Nullable __autoreleasing * _Nonnull) error +__attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); + +- (nullable PSTCKTransactionParams *) setMetadataValueArray:(nonnull NSMutableArray*)arr + forKey:(nonnull NSString*)key + error:(NSError * _Nullable __autoreleasing * _Nonnull) error +__attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); + +- (nullable PSTCKTransactionParams *) setCustomFieldValue:(nonnull NSString*)value + displayedAs:(nonnull NSString*)display_name + error:(NSError * _Nullable __autoreleasing * _Nonnull) error +__attribute__((deprecated("This SDK has been deprecated, Please refer to our new SDK: https://github.com/PaystackHQ/paystack-sdk-ios"))); +@end diff --git a/Paystack/PublicHeaders/PSTCKValidationParams.h b/Paystack/PublicHeaders/PSTCKValidationParams.h new file mode 100644 index 0000000..0e6994f --- /dev/null +++ b/Paystack/PublicHeaders/PSTCKValidationParams.h @@ -0,0 +1,17 @@ +// +// PSTCKValidationParams.h +// Paystack +// + +#import +#import "PSTCKFormEncodable.h" +/** + * Representation of a user's credit card details. You can assemble these with information that your user enters and + * then create Paystack tokens with them using an PSTCKAPIClient. @see https://paystack.com/docs/api#cards + */ +@interface PSTCKValidationParams : NSObject + +@property (nonatomic, copy, nonnull) NSString *trans; +@property (nonatomic, copy, nonnull) NSString *token; + +@end diff --git a/Paystack/PublicHeaders/Paystack.h b/Paystack/PublicHeaders/Paystack.h index 0f0f060..40b2217 100644 --- a/Paystack/PublicHeaders/Paystack.h +++ b/Paystack/PublicHeaders/Paystack.h @@ -13,6 +13,7 @@ #import "PaystackError.h" #import "PSTCKCardBrand.h" #import "PSTCKCardParams.h" +#import "PSTCKTransactionParams.h" #import "PSTCKCard.h" #import "PSTCKCardValidationState.h" #import "PSTCKCardValidator.h" diff --git a/Paystack/PublicHeaders/PaystackError.h b/Paystack/PublicHeaders/PaystackError.h index f7ca08f..e9ab693 100644 --- a/Paystack/PublicHeaders/PaystackError.h +++ b/Paystack/PublicHeaders/PaystackError.h @@ -11,11 +11,14 @@ FOUNDATION_EXPORT NSString * __nonnull const PaystackDomain; typedef NS_ENUM(NSInteger, PSTCKErrorCode) { - PSTCKConnectionError = 40, // Trouble connecting to Paystack. - PSTCKInvalidRequestError = 50, // Your request had invalid parameters. - PSTCKAPIError = 60, // General-purpose API error (should be rare). - PSTCKCardError = 70, // Something was wrong with the given card (most common). - PSTCKCheckoutError = 80, // Paystack Checkout encountered an error. + PSTCKConnectionError = 40, // Trouble connecting to Paystack. + PSTCKInvalidRequestError = 50, // Your request had invalid parameters. + PSTCKAPIError = 60, // General-purpose API error (should be rare). + PSTCKCardError = 70, // Something was wrong with the given card (most common). + PSTCKCardErrorProcessingError = 80, // Paystack Checkout encountered an error. + PSTCKTransactionError = 90, // Something was wrong with the given transaction details. + PSTCKConflictError = 100, // A transaction was started while SDK was processing another + PSTCKExpiredAccessCodeError = 110, // The access code is not usable }; #pragma mark userInfo keys @@ -48,12 +51,16 @@ FOUNDATION_EXPORT NSString * __nonnull const PSTCKIncorrectCVC; #pragma mark Strings +#define PSTCKExpiredAccessCodeErrorMessage NSLocalizedString(@"There was a problem completing your request", @"Error when access code has no valid transaction") #define PSTCKCardErrorInvalidNumberUserMessage NSLocalizedString(@"Your card's number is invalid", @"Error when the card number is not valid") +#define PSTCKCardErrorProcessingTransactionMessage NSLocalizedString(@"Please wait", @"Error when chargeCard is called while the SDK is still processing a transaction") #define PSTCKCardErrorInvalidCVCUserMessage NSLocalizedString(@"Your card's security code is invalid", @"Error when the card's CVC is not valid") #define PSTCKCardErrorInvalidExpMonthUserMessage \ NSLocalizedString(@"Your card's expiration month is invalid", @"Error when the card's expiration month is not valid") #define PSTCKCardErrorInvalidExpYearUserMessage \ - NSLocalizedString(@"Your card's expiration year is invalid", @"Error when the card's expiration year is not valid") +NSLocalizedString(@"Your card's expiration year is invalid", @"Error when the card's expiration year is not valid") +#define PSTCKTransactionErrorDontSetCustomFieldDirectlyMessage \ + NSLocalizedString(@"Please use the setCustomField function", @"Error when the app attempts to set the custom_fields key directly") #define PSTCKCardErrorExpiredCardUserMessage NSLocalizedString(@"Your card has expired", @"Error when the card has already expired") #define PSTCKCardErrorDeclinedUserMessage NSLocalizedString(@"Your card was declined", @"Error when the card was declined by the credit card networks") #define PSTCKUnexpectedError \ diff --git a/Paystack/Resources/Images/Cancel.png b/Paystack/Resources/Images/Cancel.png new file mode 100644 index 0000000..b3e279e Binary files /dev/null and b/Paystack/Resources/Images/Cancel.png differ diff --git a/Paystack/Resources/Images/Cancel@2x.png b/Paystack/Resources/Images/Cancel@2x.png new file mode 100644 index 0000000..d3b5068 Binary files /dev/null and b/Paystack/Resources/Images/Cancel@2x.png differ diff --git a/Paystack/Resources/Images/Cancel@3x.png b/Paystack/Resources/Images/Cancel@3x.png new file mode 100644 index 0000000..a198240 Binary files /dev/null and b/Paystack/Resources/Images/Cancel@3x.png differ diff --git a/Paystack/Resources/Images/pstck_card_verve.png b/Paystack/Resources/Images/pstck_card_verve.png new file mode 100644 index 0000000..5f00282 Binary files /dev/null and b/Paystack/Resources/Images/pstck_card_verve.png differ diff --git a/Paystack/Resources/Images/pstck_card_verve@2x.png b/Paystack/Resources/Images/pstck_card_verve@2x.png new file mode 100644 index 0000000..4bc92ee Binary files /dev/null and b/Paystack/Resources/Images/pstck_card_verve@2x.png differ diff --git a/Paystack/Resources/Images/pstck_card_verve@3x.png b/Paystack/Resources/Images/pstck_card_verve@3x.png new file mode 100644 index 0000000..ff7109c Binary files /dev/null and b/Paystack/Resources/Images/pstck_card_verve@3x.png differ diff --git a/Paystack/UI/PSTCKAuthViewController.h b/Paystack/UI/PSTCKAuthViewController.h new file mode 100644 index 0000000..f189adb --- /dev/null +++ b/Paystack/UI/PSTCKAuthViewController.h @@ -0,0 +1,31 @@ +// +// EBAEventbritePurchaseViewController.h +// InEvent +// +// Created by Pedro Góes on 12/16/15. +// Copyright © 2015 InEvent. All rights reserved. +// + +#import +#import + +typedef void(^PSTCKAuthCallback)(void); + +/** + * View Controller subclass containing a `UIWebView` which will be used to display the Paystack web UI to perform the authorization. + **/ +@interface PSTCKAuthViewController : UIViewController + +/** ************************************************************************************************ ** + * @name Initializers + ** ************************************************************************************************ **/ + +/** + * Default initializer. + * @param authURL the authorization url from Paystack. + * @param completion A standard block. + * @returns An initialized instance + **/ +- (id)initWithURL:(NSURL *)authURL handler:(PSTCKAuthCallback)completion; + +@end diff --git a/Paystack/UI/PSTCKAuthViewController.m b/Paystack/UI/PSTCKAuthViewController.m new file mode 100644 index 0000000..e2e25c1 --- /dev/null +++ b/Paystack/UI/PSTCKAuthViewController.m @@ -0,0 +1,124 @@ +// +// EBAEventbritePurchaseViewController.m +// InEvent +// +// Created by Pedro Góes on 12/16/15. +// Copyright © 2015 InEvent. All rights reserved. +// + +#import "PSTCKAuthViewController.h" + +@interface PSTCKAuthViewController () + +@property(nonatomic, strong) WKWebView *authenticationWebView; +@property(nonatomic, copy) PSTCKAuthCallback completion; +@property(nonatomic, strong) NSURL *authURL; + +@end + +@interface PSTCKAuthViewController (WKNavigationDelegate) + +@end + +@implementation PSTCKAuthViewController + +BOOL handlingRedirectURL; + +- (id)initWithURL:(NSURL *)authURL handler:(PSTCKAuthCallback)completion { + self = [super init]; + if (self) { + self.authURL = authURL; + self.completion = completion; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + +#ifdef __IPHONE_7_0 + self.edgesForExtendedLayout = UIRectEdgeNone; +#endif + + UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(tappedCancelButton:)]; + self.navigationItem.leftBarButtonItem = cancelButton; + // Adds javascript to make content width the device width. + NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);"; + WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; + WKUserContentController *wkUController = [[WKUserContentController alloc] init]; + [wkUController addUserScript:wkUScript]; + WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init]; + wkWebConfig.userContentController = wkUController; + + self.authenticationWebView = [[WKWebView alloc] initWithFrame:self.view.frame configuration:wkWebConfig]; + self.authenticationWebView.UIDelegate = self; + self.authenticationWebView.navigationDelegate = self; + [self.view addSubview:self.authenticationWebView]; + [self.navigationController setNavigationBarHidden:NO animated:YES]; + self.navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical; +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + [self.authenticationWebView loadRequest:[NSURLRequest requestWithURL:self.authURL]]; + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES]; +} + +- (void)viewWillLayoutSubviews { + [super viewWillLayoutSubviews]; + self.authenticationWebView.frame = [UIScreen mainScreen].bounds; +} + +#pragma mark UI Action Methods + +- (void)tappedCancelButton:(id)cancelButton { + #pragma unused(cancelButton) + self.completion(); +} + +@end + +@implementation PSTCKAuthViewController (WKNavigationDelegate) + +- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { + #pragma unused(webView, navigationAction) + NSURLRequest *request = navigationAction.request; + NSString *url = [[request URL]absoluteString]; + + // Prevent loading URL if it is the redirectURL + // The intention is to only requery 3DS auths + handlingRedirectURL = !([url rangeOfString:@"paystack.co/charge/three_d_response/"].location == NSNotFound); + + // Processing has finished? + if (handlingRedirectURL) { + self.completion(); + return decisionHandler(WKNavigationActionPolicyCancel); + + } + else { + return decisionHandler(WKNavigationActionPolicyAllow); + } +} + +- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { + #pragma unused(webView, navigation) + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; +} + +- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error { + #pragma unused(webView, navigation) + // Turn off network activity indicator upon failure to load web view + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO]; + + // In case the user hits 'Allow' before the page is fully loaded + if (error.code == NSURLErrorCancelled) { + return; + } + + // Abort if we are on Eventbrite's domain + if (!handlingRedirectURL) { + self.completion(); + } +} + +@end diff --git a/Paystack/UI/PSTCKFormTextField.m b/Paystack/UI/PSTCKFormTextField.m index d4f1db9..22b0dcd 100644 --- a/Paystack/UI/PSTCKFormTextField.m +++ b/Paystack/UI/PSTCKFormTextField.m @@ -60,6 +60,8 @@ - (NSAttributedString *)attributedStringForString:(NSString *)string attributes: PSTCKCardBrand currentBrand = [PSTCKCardValidator brandForNumber:attributedString.string]; if (currentBrand == PSTCKCardBrandAmex) { cardSpacing = @[@3, @9]; + } else if (currentBrand == PSTCKCardBrandVerve) { + cardSpacing = @[@2, @6, @10, @14]; } else { cardSpacing = @[@3, @7, @11]; } diff --git a/Paystack/UI/PSTCKPaymentCardTextField.m b/Paystack/UI/PSTCKPaymentCardTextField.m index ded6684..0e326fa 100644 --- a/Paystack/UI/PSTCKPaymentCardTextField.m +++ b/Paystack/UI/PSTCKPaymentCardTextField.m @@ -13,7 +13,7 @@ #import "UIImage+Paystack.h" #define FAUXPAS_IGNORED_IN_METHOD(...) - +__deprecated @interface PSTCKPaymentCardTextField() @property(nonatomic, readwrite, strong)PSTCKFormTextField *sizingField; @@ -25,6 +25,9 @@ @interface PSTCKPaymentCardTextField() @property(nonatomic, readwrite, weak)PSTCKFormTextField *expirationField; +@property(nonatomic, readwrite, weak)UIButton *bumpToExpField; + + @property(nonatomic, readwrite, weak)PSTCKFormTextField *cvcField; @property(nonatomic, readwrite, strong)PSTCKPaymentCardTextFieldViewModel *viewModel; @@ -32,6 +35,7 @@ @interface PSTCKPaymentCardTextField() @property(nonatomic, readwrite, weak)UITextField *selectedField; @property(nonatomic, assign)BOOL numberFieldShrunk; +@property(nonatomic, assign)BOOL bumped; @end @@ -99,7 +103,16 @@ - (void)commonInit { expirationField.alpha = 0; self.expirationField = expirationField; self.expirationPlaceholder = @"MM/YY"; - + + UIButton *bumpToExpField = [UIButton buttonWithType:UIButtonTypeCustom]; + [bumpToExpField addTarget:self action:@selector(tappedBumpButton:) forControlEvents:UIControlEventTouchUpInside]; + [bumpToExpField setTitle: @">" forState: UIControlStateNormal]; + [bumpToExpField setTitleColor:self.textColor forState:UIControlStateNormal]; + bumpToExpField.hidden = YES; + self.bumpToExpField.alpha = 50; + self.bumpToExpField = bumpToExpField; + self.bumped = NO; + PSTCKFormTextField *cvcField = [self buildTextField]; cvcField.tag = PSTCKCardFieldTypeCVC; cvcField.alpha = 0; @@ -116,6 +129,16 @@ - (void)commonInit { [self.fieldsView addSubview:expirationField]; [self.fieldsView addSubview:numberField]; [self addSubview:brandImageView]; + [self addSubview:bumpToExpField]; +} + +- (void)tappedBumpButton:(id)bumpButton { +#pragma unused(bumpButton) + self.bumped = YES; + self.bumpToExpField.hidden = YES; + if([self.viewModel validationStateForField:PSTCKCardFieldTypeNumber]==PSTCKCardValidationStateValid){ + [self selectNextField]; + } } - (PSTCKPaymentCardTextFieldViewModel *)viewModel { @@ -334,6 +357,18 @@ - (BOOL)selectPreviousField { - (PSTCKFormTextField *)nextField { if (self.selectedField == self.numberField) { + Boolean stay = NO; + if(self.viewModel.brand==PSTCKCardBrandVerve){ + stay = (self.numberField.text.length < 19); + if(stay){ + stay = !self.bumped; + } + } + self.bumpToExpField.hidden = !stay; + self.bumped = NO; + if(stay){ + return self.numberField; + } if ([self.viewModel validationStateForField:self.expirationField.tag] == PSTCKCardValidationStateValid) { return self.cvcField; } @@ -479,6 +514,10 @@ - (CGRect)brandImageRectForBounds:(CGRect)bounds { return CGRectMake(PSTCKPaymentCardTextFieldDefaultPadding, 2, self.brandImageView.image.size.width, bounds.size.height - 2); } +- (CGRect)bumpRectForBounds:(CGRect)bounds { + return CGRectMake(CGRectGetWidth(bounds) - [self widthForText:@" > "] - (PSTCKPaymentCardTextFieldDefaultPadding / 2) , 2, [self widthForText:@" > "], bounds.size.height - 2); +} + - (CGRect)fieldsRectForBounds:(CGRect)bounds { CGRect brandImageRect = [self brandImageRectForBounds:bounds]; return CGRectMake(CGRectGetMaxX(brandImageRect), 0, CGRectGetWidth(bounds) - CGRectGetMaxX(brandImageRect), CGRectGetHeight(bounds)); @@ -517,6 +556,7 @@ - (void)layoutSubviews { CGRect bounds = self.bounds; + self.bumpToExpField.frame = [self bumpRectForBounds:bounds]; self.brandImageView.frame = [self brandImageRectForBounds:bounds]; self.fieldsView.frame = [self fieldsRectForBounds:bounds]; self.numberField.frame = [self numberFieldRectForBounds:bounds]; @@ -555,7 +595,7 @@ - (void)setNumberFieldShrunk:(BOOL)shrunk animated:(BOOL)animated } _numberFieldShrunk = shrunk; - void (^animations)() = ^void() { + void (^animations)(void) = ^void() { for (UIView *view in @[self.expirationField, self.cvcField]) { view.alpha = 1.0f * shrunk; } diff --git a/Paystack/UI/PSTCKPaymentCardTextFieldViewModel.m b/Paystack/UI/PSTCKPaymentCardTextFieldViewModel.m index 65ded0a..7b23f75 100644 --- a/Paystack/UI/PSTCKPaymentCardTextFieldViewModel.m +++ b/Paystack/UI/PSTCKPaymentCardTextFieldViewModel.m @@ -103,7 +103,7 @@ - (BOOL)isValid { } - (NSString *)defaultPlaceholder { - return @"1234567812345678"; + return @"1234 5678 1234 5678 000"; } - (NSString *)numberWithoutLastDigits { diff --git a/Paystack/UI/UIImage+Paystack.m b/Paystack/UI/UIImage+Paystack.m index 8268b25..08915a3 100644 --- a/Paystack/UI/UIImage+Paystack.m +++ b/Paystack/UI/UIImage+Paystack.m @@ -63,6 +63,9 @@ + (UIImage *)pstck_brandImageForCardBrand:(PSTCKCardBrand)brand { case PSTCKCardBrandMasterCard: imageName = @"pstck_card_mastercard"; break; + case PSTCKCardBrandVerve: + imageName = @"pstck_card_verve"; + break; case PSTCKCardBrandUnknown: imageName = templateSupported ? @"pstck_card_placeholder_template" : @"pstck_card_placeholder"; break; diff --git a/Paystack/Validator/RequiredRule.swift b/Paystack/Validator/RequiredRule.swift new file mode 100644 index 0000000..dc87ce7 --- /dev/null +++ b/Paystack/Validator/RequiredRule.swift @@ -0,0 +1,46 @@ +// +// Required.swift +// pyur-ios +// +// Created by Jeff Potter on 12/22/14. +// Copyright (c) 2015 jpotts18. All rights reserved. +// + +import Foundation + +/** + `RequiredRule` is a subclass of Rule that defines how a required field is validated. + */ +open class RequiredRule: Rule { + /// String that holds error message. + private var message : String + + /** + Initializes `RequiredRule` object with error message. Used to validate a field that requires text. + + - parameter message: String of error message. + - returns: An initialized `RequiredRule` object, or nil if an object could not be created for some reason that would not result in an exception. + */ + public init(message : String = "This field is required"){ + self.message = message + } + + /** + Validates a field. + + - parameter value: String to check for validation. + - returns: Boolean value. True if validation is successful; False if validation fails. + */ + open func validate(_ value: String) -> Bool { + return !value.isEmpty + } + + /** + Used to display error message when validation fails. + + - returns: String of error message. + */ + open func errorMessage() -> String { + return message + } +} diff --git a/Paystack/Validator/Rule.swift b/Paystack/Validator/Rule.swift new file mode 100644 index 0000000..2f621c9 --- /dev/null +++ b/Paystack/Validator/Rule.swift @@ -0,0 +1,27 @@ +// +// Validation.swift +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2015 jpotts18. All rights reserved. +// + +import Foundation + +/** + The `Rule` protocol declares the required methods for all objects that subscribe to it. + */ +public protocol Rule { + /** + Validates text of a field. + + - parameter value: String of text to be validated. + - returns: Boolean value. True if validation is successful; False if validation fails. + */ + func validate(_ value: String) -> Bool + /** + Displays error message of a field that has failed validation. + + - returns: String of error message. + */ + func errorMessage() -> String +} diff --git a/Paystack/Validator/Validatable.swift b/Paystack/Validator/Validatable.swift new file mode 100644 index 0000000..4778f3a --- /dev/null +++ b/Paystack/Validator/Validatable.swift @@ -0,0 +1,33 @@ +// +// Validatable.swift +// Validator +// +// Created by Deniz Adalar on 28/04/16. +// Copyright © 2016 jpotts18. All rights reserved. +// + +import Foundation + +public typealias ValidatableField = AnyObject & Validatable + +public protocol Validatable { + + var validationText: String { + get + } +} + +extension UITextField: Validatable { + + open var validationText: String { + return text ?? "" + } +} + +extension UITextView: Validatable { + + public var validationText: String { + return text ?? "" + } +} + diff --git a/Paystack/Validator/ValidationDelegate.swift b/Paystack/Validator/ValidationDelegate.swift new file mode 100644 index 0000000..0e073ed --- /dev/null +++ b/Paystack/Validator/ValidationDelegate.swift @@ -0,0 +1,27 @@ +// +// ValidationDelegate.swift +// Validator +// +// Created by David Patterson on 1/2/16. +// Copyright © 2016 jpotts18. All rights reserved. +// + +import Foundation +import UIKit +/** + Protocol for `ValidationDelegate` adherents, which comes with two required methods that are called depending on whether validation succeeded or failed. + */ +public protocol ValidationDelegate { + /** + This method will be called on delegate object when validation is successful. + + - returns: No return value. + */ + func validationSuccessful() + /** + This method will be called on delegate object when validation fails. + + - returns: No return value. + */ + func validationFailed(_ errors: [(Validatable, ValidationError)]) +} diff --git a/Paystack/Validator/ValidationError.swift b/Paystack/Validator/ValidationError.swift new file mode 100644 index 0000000..98ab4de --- /dev/null +++ b/Paystack/Validator/ValidationError.swift @@ -0,0 +1,33 @@ +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2015 jpotts18. All rights reserved. +// + +import Foundation +import UIKit + +/** + The `ValidationError` class is used for representing errors of a failed validation. It contains the field, error label, and error message of a failed validation. + */ +public class ValidationError: NSObject { + /// the Validatable field of the field + public let field:ValidatableField + /// the error label of the field + public var errorLabel:UILabel? + /// the error message of the field + public let errorMessage:String + + /** + Initializes `ValidationError` object with a field, errorLabel, and errorMessage. + + - parameter field: Validatable field that holds field. + - parameter errorLabel: UILabel that holds error label. + - parameter errorMessage: String that holds error message. + - returns: An initialized object, or nil if an object could not be created for some reason that would not result in an exception. + */ + public init(field:ValidatableField, errorLabel:UILabel?, error:String){ + self.field = field + self.errorLabel = errorLabel + self.errorMessage = error + } +} \ No newline at end of file diff --git a/Paystack/Validator/ValidationRule.swift b/Paystack/Validator/ValidationRule.swift new file mode 100644 index 0000000..ef8d306 --- /dev/null +++ b/Paystack/Validator/ValidationRule.swift @@ -0,0 +1,45 @@ +// +// ValidationRule.swift +// +// Created by Jeff Potter on 11/11/14. +// Copyright (c) 2015 jpotts18. All rights reserved. +// + +import Foundation +import UIKit + +/** + `ValidationRule` is a class that creates an object which holds validation info of a field. + */ +public class ValidationRule { + /// the field of the field + public var field:ValidatableField + /// the errorLabel of the field + public var errorLabel:UILabel? + /// the rules of the field + public var rules:[Rule] = [] + + /** + Initializes `ValidationRule` instance with field, rules, and errorLabel. + + - parameter field: field that holds actual text in field. + - parameter errorLabel: label that holds error label of field. + - parameter rules: array of Rule objects, which field will be validated against. + - returns: An initialized `ValidationRule` object, or nil if an object could not be created for some reason that would not result in an exception. + */ + public init(field: ValidatableField, rules:[Rule], errorLabel:UILabel?){ + self.field = field + self.errorLabel = errorLabel + self.rules = rules + } + + /** + Used to validate field against its validation rules. + - returns: `ValidationError` object if at least one error is found. Nil is returned if there are no validation errors. + */ + public func validateField() -> ValidationError? { + return rules.filter{ + return !$0.validate(field.validationText) + }.map{ rule -> ValidationError in return ValidationError(field: self.field, errorLabel:self.errorLabel, error: rule.errorMessage()) }.first + } +} diff --git a/Paystack/Validator/Validator.swift b/Paystack/Validator/Validator.swift new file mode 100644 index 0000000..2553e47 --- /dev/null +++ b/Paystack/Validator/Validator.swift @@ -0,0 +1,154 @@ +// +// Validator.swift +// +// Created by Jeff Potter on 11/10/14. +// Copyright (c) 2015 jpotts18. All rights reserved. +// + +import Foundation +import UIKit + +/** + Class that makes `Validator` objects. Should be added as a parameter to ViewController that will display + validation fields. + */ +public class Validator { + /// Dictionary to hold all fields (and accompanying rules) that will undergo validation. + public var validations = ValidatorDictionary() + /// Dictionary to hold fields (and accompanying errors) that were unsuccessfully validated. + public var errors = ValidatorDictionary() + /// Dictionary to hold fields by their object identifiers + private var fields = ValidatorDictionary() + /// Variable that holds success closure to display positive status of field. + private var successStyleTransform:((_ validationRule:ValidationRule)->Void)? + /// Variable that holds error closure to display negative status of field. + private var errorStyleTransform:((_ validationError:ValidationError)->Void)? + /// - returns: An initialized object, or nil if an object could not be created for some reason that would not result in an exception. + public init(){} + + // MARK: Private functions + + /** + This method is used to validate all fields registered to Validator. If validation is unsuccessful, + field gets added to errors dictionary. + + - returns: No return value. + */ + private func validateAllFields() { + + errors = ValidatorDictionary() + + for (_, rule) in validations { + if let error = rule.validateField() { + errors[rule.field] = error + + // let the user transform the field if they want + if let transform = self.errorStyleTransform { + transform(error) + } + } else { + // No error + // let the user transform the field if they want + if let transform = self.successStyleTransform { + transform(rule) + } + } + } + } + + // MARK: Public functions + + /** + This method is used to validate a single field registered to Validator. If validation is unsuccessful, + field gets added to errors dictionary. + + - parameter field: Holds validator field data. + - returns: No return value. + */ + public func validateField(_ field: ValidatableField, callback: (_ error:ValidationError?) -> Void){ + if let fieldRule = validations[field] { + if let error = fieldRule.validateField() { + errors[field] = error + if let transform = self.errorStyleTransform { + transform(error) + } + callback(error) + } else { + if let transform = self.successStyleTransform { + transform(fieldRule) + } + callback(nil) + } + } else { + callback(nil) + } + } + + // MARK: Using Keys + + /** + This method is used to style fields that have undergone validation checks. Success callback should be used to show common success styling and error callback should be used to show common error styling. + + - parameter success: A closure which is called with validationRule, an object that holds validation data + - parameter error: A closure which is called with validationError, an object that holds validation error data + - returns: No return value + */ + public func styleTransformers(success:((_ validationRule:ValidationRule)->Void)?, error:((_ validationError:ValidationError)->Void)?) { + self.successStyleTransform = success + self.errorStyleTransform = error + } + + /** + This method is used to add a field to validator. + + - parameter field: field that is to be validated. + - parameter errorLabel: A UILabel that holds error label data + - parameter rules: A Rule array that holds different rules that apply to said field. + - returns: No return value + */ + public func registerField(_ field: ValidatableField, errorLabel:UILabel? = nil, rules:[Rule]) { + validations[field] = ValidationRule(field: field, rules:rules, errorLabel:errorLabel) + fields[field] = field + } + + /** + This method is for removing a field validator. + + - parameter field: field used to locate and remove field from validator. + - returns: No return value + */ + public func unregisterField(_ field:ValidatableField) { + validations.removeValueForKey(field) + errors.removeValueForKey(field) + } + + /** + This method checks to see if all fields in validator are valid. + + - returns: No return value. + */ + public func validate(_ delegate:ValidationDelegate) { + + self.validateAllFields() + + if errors.isEmpty { + delegate.validationSuccessful() + } else { + delegate.validationFailed(errors.map { (fields[$1.field]!, $1) }) + } + + } + + /** + This method validates all fields in validator and sets any errors to errors parameter of callback. + + - parameter callback: A closure which is called with errors, a dictionary of type Validatable:ValidationError. + - returns: No return value. + */ + public func validate(_ callback:(_ errors:[(Validatable, ValidationError)])->Void) -> Void { + + self.validateAllFields() + + callback(errors.map { (fields[$1.field]!, $1) } ) + } +} diff --git a/Paystack/Validator/ValidatorDictionary.swift b/Paystack/Validator/ValidatorDictionary.swift new file mode 100644 index 0000000..e152b0f --- /dev/null +++ b/Paystack/Validator/ValidatorDictionary.swift @@ -0,0 +1,45 @@ +// +// ValidatorDictionary.swift +// Validator +// +// Created by Deniz Adalar on 04/05/16. +// Copyright © 2016 jpotts18. All rights reserved. +// + +import Foundation + +public struct ValidatorDictionary : Sequence { + + private var innerDictionary: [ObjectIdentifier: T] = [:]; + + public subscript(key: ValidatableField?) -> T? { + get { + if let key = key { + return innerDictionary[ObjectIdentifier(key)]; + } else { + return nil; + } + } + set(newValue) { + if let key = key { + innerDictionary[ObjectIdentifier(key)] = newValue; + } + } + } + + public mutating func removeAll() { + innerDictionary.removeAll() + } + + public mutating func removeValueForKey(_ key: ValidatableField) { + innerDictionary.removeValue(forKey: ObjectIdentifier(key)) + } + + public var isEmpty: Bool { + return innerDictionary.isEmpty + } + + public func makeIterator() -> DictionaryIterator { + return innerDictionary.makeIterator() + } +} diff --git a/Paystack/module.modulemap b/Paystack/module.modulemap deleted file mode 100644 index 9b84f78..0000000 --- a/Paystack/module.modulemap +++ /dev/null @@ -1,5 +0,0 @@ -framework module Paystack { - umbrella header "Paystack.h" - export * - module * { export * } -} \ No newline at end of file diff --git a/README.md b/README.md index 29a64fb..4e1ca48 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,26 @@ # Paystack iOS SDK -[![CocoaPods](https://img.shields.io/cocoapods/v/Paystack.svg?style=flat)](http://cocoapods.org/?q=author%3Apaystack%20name%3Apaystack) +[![CocoaPods](https://img.shields.io/cocoapods/v/Paystack.svg?style=flat)](https://cocoapods.org/pods/Paystack) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![CocoaPods](https://img.shields.io/cocoapods/l/Paystack.svg?style=flat)](https://github.com/paystackhq/paystack-ios/blob/master/LICENSE) [![CocoaPods](https://img.shields.io/cocoapods/p/Paystack.svg?style=flat)](https://github.com/paystackhq/paystack-ios#) -The Paystack iOS SDK make it easy to collect your users' credit card details inside your iOS app. By creating tokens, -Paystack handles the bulk of PCI compliance by preventing sensitive card data from hitting your server. +The Paystack iOS SDK make it easy to collect your users' credit card details inside your iOS app. By charging the card immediately on our +servers, Paystack handles the bulk of PCI compliance by preventing sensitive card data from hitting your server. -This library helps collect card details on iOS and OSX, getting a token. This shoulders the burden of PCI compliance by helping you avoid the need to send -card data directly to your server. Instead you send to Paystack's server and get a token which you can charge once in your server-side code. This charge -returns an `authorization_code` if successful. Subsequent charges can then be made using the `authorization_code`. +This library helps collect card details on iOS, completing a charge. This shoulders the burden of PCI compliance by helping you avoid the +need to send card data directly to your server. Instead you send to Paystack's server and get a reference which you can verify in your +server-side code. The verify call returns an `authorization_code`. Subsequent charges can then be made using the `authorization_code`. ## Requirements -Our SDK is compatible with iOS apps supporting iOS 7.0 and above. It requires Xcode 7.0+ to build the source. +Our SDK is compatible with iOS apps supporting iOS 8.0 and above. It requires Xcode 8.0+ to build the source. -You also need to add Keychain Sharing entitlements for your app. +**You will also need to add Keychain Sharing entitlements for your app.** ## Integration -We've written a [guide](GUIDE.md) that explains everything from installation, to creating payment tokens and more. +We've written a [guide](GUIDE.md) that explains everything from installation, to charging cards and more. ## Example app @@ -31,24 +31,31 @@ To build and run the example apps, open `Paystack.xcworkspace` and choose the ap ### Getting started with the Simple iOS Example App -Note: The example app requires Xcode 7.0 to build and run. +Note: The example app requires Xcode 8.0 to build and run. -Before you can run the app, you need to provide it with your Paystack publishable key. +Before you can run the app, you need to provide it with your Paystack public key. 1. If you haven't already, sign up for a [Paystack account](https://dashboard.paystack.com/#/signup) (it takes seconds). Then go to https://dashboard.paystack.co/#/settings/developer. -2. Replace the `paystackPublishableKey` constant in ViewController.swift (for the Sample app) with your Test Publishable Key. -3. Head to https://github.com/paystackhq/sample-charge-token-backend and click "Deploy to Heroku" (you may have to sign up for a Heroku account as part of this process). Provide your Paystack test secret key for the `PAYSTACK_TEST_SECRET_KEY` field under 'Env'. Click "Deploy for Free". -4. Replace the `backendChargeURLString` variable in the example iOS app with the app URL Heroku provides you with (e.g. "https://my-example-app.herokuapp.com") +2. Replace the `paystackPublicKey` constant in ViewController.swift (for the Sample app) with your Test Public Key. +3. Head to https://github.com/paystackhq/sample-charge-card-backend and click "Deploy to Heroku" (you may have to sign up for a Heroku account as part of this process). Provide your Paystack test secret key for the `PAYSTACK_TEST_SECRET_KEY` field under 'Env'. Click "Deploy for Free". +4. Replace the `backendURLString` variable in the example iOS app with the app URL Heroku provides you with (e.g. "https://my-example-app.herokuapp.com") **WITHOUT THE TRAILING '/'** -After this is done, you can make test payments through the app (use credit card number 4123 4501 3100 1381, along with 883 as cvc and any future expiration date) and then view the payments in your Paystack Dashboard! +### Making a test Charge -And the return value from the backend will be displayed in your Output window. +After completing the steps required above, you can (and should) test your implementation of the Paystack iOS library in your iOS app. You need the details of an actual debit/credit card to do this, so we provide ##_test cards_## for your use instead of using your own debit/credit cards. -## Running the tests +You will find test cards on [this Paystack documentation page](https://developers.paystack.co/docs/test-cards). -1. Open Paystack.xcworkspace -1. Choose the "iOS Tests" or "OS X Tests" scheme -1. Run Product -> Test +To try out the OTP flow, we have provided a test "verve" card: + +``` +50606 66666 66666 6666 +CVV: 123 +PIN: 1234 +TOKEN: 123456 +``` + +You can then view the payments in your Paystack Dashboard! ## Misc. notes @@ -56,19 +63,4 @@ And the return value from the backend will be displayed in your Output window. See [PaystackError.h](https://github.com/paystackhq/paystack-ios/blob/master/Paystack/PublicHeaders/PaystackError.h) for a list of error codes that may be returned from the Paystack API. -### Validating PSTCKCards - -You have a few options for handling validation of credit card data on the client, depending on what your application does. Client-side validation of credit card data is not required since our API will correctly reject invalid card information, but can be useful to validate information as soon as a user enters it, or simply to save a network request. - -The simplest thing you can do is to populate an `PSTCKCard` object and, before sending the request, call `- (BOOL)validateCardReturningError:` on the card. This validates the entire card object, but is not useful for validating card properties one at a time. - -To validate `PSTCKCard` properties individually, you should use the following: - - - (BOOL)validateNumber:error: - - (BOOL)validateCvc:error: - - (BOOL)validateExpMonth:error: - - (BOOL)validateExpYear:error: - -These methods follow the validation method convention used by [key-value validation](http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/KeyValueCoding/Articles/Validation.html). So, you can use these methods by invoking them directly, or by calling `[card validateValue:forKey:error]` for a property on the `PSTCKCard` object. -When using these validation methods, you will want to set the property on your card object when a property does validate before validating the next property. This allows the methods to use existing properties on the card correctly to validate a new property. For example, validating `5` for the `expMonth` property will return YES if no `expYear` is set. But if `expYear` is set and you try to set `expMonth` to 5 and the combination of `expMonth` and `expYear` is in the past, `5` will not validate. The order in which you call the validate methods does not matter for this though. diff --git a/Tests/Tests/PSTCKAPIClientTest.m b/Tests/Tests/PSTCKAPIClientTest.m index 02bfe60..e41fd53 100644 --- a/Tests/Tests/PSTCKAPIClientTest.m +++ b/Tests/Tests/PSTCKAPIClientTest.m @@ -16,10 +16,10 @@ - (void)testSharedClient { XCTAssertEqualObjects([PSTCKAPIClient sharedClient], [PSTCKAPIClient sharedClient]); } -- (void)testPublishableKey { - [Paystack setDefaultPublishableKey:@"test"]; +- (void)testPublicKey { + [Paystack setDefaultPublicKey:@"test"]; PSTCKAPIClient *client = [PSTCKAPIClient sharedClient]; - XCTAssertEqualObjects(client.publishableKey, @"test"); + XCTAssertEqualObjects(client.publicKey, @"test"); } @end diff --git a/Tests/Tests/PSTCKCardFunctionalTest.m b/Tests/Tests/PSTCKCardFunctionalTest.m deleted file mode 100644 index 53db5b8..0000000 --- a/Tests/Tests/PSTCKCardFunctionalTest.m +++ /dev/null @@ -1,94 +0,0 @@ -// -// PSTCKCardFunctionalTest.m -// Paystack -// - -@import XCTest; - -#import "Paystack.h" - -@interface PSTCKCardFunctionalTest : XCTestCase -@end - -@implementation PSTCKCardFunctionalTest - -- (void)testCreateCardToken { - PSTCKCardParams *card = [[PSTCKCardParams alloc] init]; - - card.number = @"4242424242424242"; - card.cvc = @"222"; - card.expMonth = 11; - card.expYear = 2018; - card.currency = @"usd"; - card.addressLine1 = @"123 Fake Street"; - card.addressLine2 = @"Apartment 4"; - card.addressCity = @"New York"; - card.addressState = @"NY"; - card.addressCountry = @"USA"; - card.addressZip = @"10002"; - - PSTCKAPIClient *client = [[PSTCKAPIClient alloc] initWithPublishableKey:@"pk_test_vOo1umqsYxSrP5UXfOeL3ecm"]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Card creation"]; - - [client createTokenWithCard:card - completion:^(PSTCKToken *token, NSError *error) { - [expectation fulfill]; - - XCTAssertNil(error, @"error should be nil %@", error.localizedDescription); - XCTAssertNotNil(token, @"token should not be nil"); - - XCTAssertNotNil(token.tokenId); -// XCTAssertEqual(6U, token.card.expMonth); -// XCTAssertEqual(2018U, token.card.expYear); - XCTAssertEqualObjects(@"4242", token.last4); -// XCTAssertEqualObjects(@"usd", token.card.currency); - }]; - [self waitForExpectationsWithTimeout:60.0f handler:nil]; -} - -- (void)testCardTokenCreationWithInvalidParams { - PSTCKCardParams *card = [[PSTCKCardParams alloc] init]; - - card.number = @"4242 4242 4242 4241"; - card.expMonth = 6; - card.expYear = 2018; - - PSTCKAPIClient *client = [[PSTCKAPIClient alloc] initWithPublishableKey:@"pk_test_vOo1umqsYxSrP5UXfOeL3ecm"]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Card creation"]; - - [client createTokenWithCard:card - completion:^(PSTCKToken *token, NSError *error) { - [expectation fulfill]; - - XCTAssertNotNil(error, @"error should not be nil"); - XCTAssertEqual(error.code, 60); - XCTAssertEqualObjects(error.domain, PaystackDomain); -// XCTAssertEqualObjects(error.userInfo[PSTCKErrorParameterKey], @"number"); - XCTAssertNil(token, @"token should be nil: %@", token.description); - }]; - [self waitForExpectationsWithTimeout:5.0f handler:nil]; -} - -- (void)testInvalidKey { - PSTCKCardParams *card = [[PSTCKCardParams alloc] init]; - - card.number = @"4242 4242 4242 4242"; - card.expMonth = 6; - card.expYear = 2018; - - PSTCKAPIClient *client = [[PSTCKAPIClient alloc] initWithPublishableKey:@"not_a_valid_key_asdf"]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Card failure"]; - [client createTokenWithCard:card - completion:^(PSTCKToken *token, NSError *error) { - [expectation fulfill]; - XCTAssertNil(token, @"token should be nil"); - XCTAssertNotNil(error, @"error should not be nil"); - XCTAssert([error.localizedDescription rangeOfString:@"asdf"].location != NSNotFound, @"error should contain last 4 of key"); - }]; - [self waitForExpectationsWithTimeout:5.0f handler:nil]; -} - -@end diff --git a/Tests/Tests/PSTCKCardTest.m b/Tests/Tests/PSTCKCardTest.m deleted file mode 100644 index dc61508..0000000 --- a/Tests/Tests/PSTCKCardTest.m +++ /dev/null @@ -1,106 +0,0 @@ -// -// PSTCKCardTest.m -// Paystack -// - -@import XCTest; - -#import "PSTCKFormEncoder.h" -#import "PSTCKCard.h" -#import "PaystackError.h" - -@interface PSTCKCardTest : XCTestCase -@property (nonatomic) PSTCKCardParams *card; -@end - -@implementation PSTCKCardTest - -- (void)setUp { - _card = [[PSTCKCardParams alloc] init]; -} - -#pragma mark Helpers -- (NSDictionary *)completeAttributeDictionary { - return @{ - @"id": @"1", - @"exp_month": @"12", - @"exp_year": @"2013", - @"name": @"Smerlock Smolmes", - @"address_line1": @"221A Baker Street", - @"address_city": @"New York", - @"address_state": @"NY", - @"address_zip": @"12345", - @"address_country": @"USA", - @"last4": @"1234", - @"dynamic_last4": @"5678", - @"brand": @"MasterCard", - @"country": @"Japan", - @"currency": @"usd", - }; -} - -- (void)testInitializingCardWithAttributeDictionary { - NSMutableDictionary *apiResponse = [[self completeAttributeDictionary] mutableCopy]; - apiResponse[@"foo"] = @"bar"; - apiResponse[@"nested"] = @{@"baz": @"bang"}; - - - PSTCKCard *cardWithAttributes = [PSTCKCard decodedObjectFromAPIResponse:apiResponse]; - XCTAssertTrue([cardWithAttributes expMonth] == 12, @"expMonth is set correctly"); - XCTAssertTrue([cardWithAttributes expYear] == 2013, @"expYear is set correctly"); - XCTAssertEqualObjects([cardWithAttributes name], @"Smerlock Smolmes", @"name is set correctly"); - XCTAssertEqualObjects([cardWithAttributes addressLine1], @"221A Baker Street", @"addressLine1 is set correctly"); - XCTAssertEqualObjects([cardWithAttributes addressCity], @"New York", @"addressCity is set correctly"); - XCTAssertEqualObjects([cardWithAttributes addressState], @"NY", @"addressState is set correctly"); - XCTAssertEqualObjects([cardWithAttributes addressZip], @"12345", @"addressZip is set correctly"); - XCTAssertEqualObjects([cardWithAttributes addressCountry], @"USA", @"addressCountry is set correctly"); - XCTAssertEqualObjects([cardWithAttributes last4], @"1234", @"last4 is set correctly"); - XCTAssertEqualObjects([cardWithAttributes dynamicLast4], @"5678", @"last4 is set correctly"); - XCTAssertEqual([cardWithAttributes brand], PSTCKCardBrandMasterCard, @"type is set correctly"); - XCTAssertEqualObjects([cardWithAttributes country], @"Japan", @"country is set correctly"); - XCTAssertEqualObjects([cardWithAttributes currency], @"usd", @"currency is set correctly"); - - NSDictionary *allResponseFields = cardWithAttributes.allResponseFields; - XCTAssertEqual(allResponseFields[@"foo"], @"bar"); - XCTAssertEqual(allResponseFields[@"last4"], @"1234"); - XCTAssertEqualObjects(allResponseFields[@"nested"], @{@"baz": @"bang"}); - XCTAssertNil(allResponseFields[@"baz"]); -} - -#pragma mark - last4 tests -- (void)testLast4ReturnsCardNumberLast4WhenNotSet { - self.card.number = @"4242424242424242"; - XCTAssertEqualObjects(self.card.last4, @"4242", @"last4 correctly returns the last 4 digits of the card number"); -} - -- (void)testLast4ReturnsNullWhenNoCardNumberSet { - XCTAssertEqualObjects(nil, self.card.last4, @"last4 returns nil when nothing is set"); -} - -- (void)testLast4ReturnsNullWhenCardNumberIsLessThanLength4 { - self.card.number = @"123"; - XCTAssertEqualObjects(nil, self.card.last4, @"last4 returns nil when number length is < 3"); -} - -- (void)testCardEquals { - PSTCKCard *card1 = [PSTCKCard decodedObjectFromAPIResponse:[self completeAttributeDictionary]]; - PSTCKCard *card2 = [PSTCKCard decodedObjectFromAPIResponse:[self completeAttributeDictionary]]; - - XCTAssertEqualObjects(card1, card1, @"card should equal itself"); - XCTAssertEqualObjects(card1, card2, @"cards with equal data should be equal"); -} - -#pragma mark - validation tests -- (void)testValidateCardReturningError_january { - PSTCKCardParams *params = [[PSTCKCardParams alloc] init]; - params.number = @"4242424242424242"; - params.expMonth = 01; - params.expYear = 18; - params.cvc = @"123"; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated" - XCTAssert([params validateCardReturningError:nil]); -#pragma clang diagnostic pop -} - -@end diff --git a/Tests/Tests/PSTCKCardValidatorTest.m b/Tests/Tests/PSTCKCardValidatorTest.m index c682674..cc8d785 100644 --- a/Tests/Tests/PSTCKCardValidatorTest.m +++ b/Tests/Tests/PSTCKCardValidatorTest.m @@ -22,15 +22,7 @@ + (NSArray *)cardData { @[@(PSTCKCardBrandMasterCard), @"5555555555554444", @(PSTCKCardValidationStateValid)], @[@(PSTCKCardBrandMasterCard), @"5200828282828210", @(PSTCKCardValidationStateValid)], @[@(PSTCKCardBrandMasterCard), @"5105105105105100", @(PSTCKCardValidationStateValid)], - @[@(PSTCKCardBrandAmex), @"378282246310005", @(PSTCKCardValidationStateValid)], - @[@(PSTCKCardBrandAmex), @"371449635398431", @(PSTCKCardValidationStateValid)], - @[@(PSTCKCardBrandDiscover), @"6011111111111117", @(PSTCKCardValidationStateValid)], - @[@(PSTCKCardBrandDiscover), @"6011000990139424", @(PSTCKCardValidationStateValid)], - @[@(PSTCKCardBrandDinersClub), @"30569309025904", @(PSTCKCardValidationStateValid)], - @[@(PSTCKCardBrandDinersClub), @"38520000023237", @(PSTCKCardValidationStateValid)], - @[@(PSTCKCardBrandJCB), @"3530111333300000", @(PSTCKCardValidationStateValid)], - @[@(PSTCKCardBrandJCB), @"3566002020360505", @(PSTCKCardValidationStateValid)], - @[@(PSTCKCardBrandUnknown), @"1234567812345678", @(PSTCKCardValidationStateInvalid)], + @[@(PSTCKCardBrandUnknown), @"1234567812345678", @(PSTCKCardValidationStateInvalid)] ]; } @@ -72,10 +64,6 @@ - (void)testNumberValidation { NSArray *possibleCardNumbers = @[ @"4242", @"5", - @"3", - @"", - @" ", - @"6011", ]; for (NSString *card in possibleCardNumbers) { @@ -110,7 +98,7 @@ - (void)testBrandNumberLength { @[@(PSTCKCardBrandDiscover), @16], @[@(PSTCKCardBrandDinersClub), @14], @[@(PSTCKCardBrandJCB), @16], - @[@(PSTCKCardBrandUnknown), @16], + @[@(PSTCKCardBrandUnknown), @20], ]; for (NSArray *test in tests) { XCTAssertEqualObjects(@([PSTCKCardValidator lengthForCardBrand:[test[0] integerValue]]), test[1]); @@ -213,14 +201,13 @@ - (void)testCVCValidation { - (void)testCardValidation { NSArray *tests = @[ - @[@"4242424242424242", @(12), @(15), @"123", @(PSTCKCardValidationStateValid)], + @[@"4242424242424242", @(12), @(2022), @"123", @(PSTCKCardValidationStateValid)], @[@"4242424242424242", @(12), @(15), @"x", @(PSTCKCardValidationStateInvalid)], - @[@"4242424242424242", @(12), @(15), @"1", @(PSTCKCardValidationStateIncomplete)], + @[@"4242424242424242", @(12), @(2023), @"1", @(PSTCKCardValidationStateIncomplete)], @[@"4242424242424242", @(12), @(14), @"123", @(PSTCKCardValidationStateInvalid)], @[@"4242424242424242", @(21), @(15), @"123", @(PSTCKCardValidationStateInvalid)], - @[@"42424242", @(12), @(15), @"123", @(PSTCKCardValidationStateIncomplete)], - @[@"378282246310005", @(12), @(15), @"1234", @(PSTCKCardValidationStateValid)], - @[@"378282246310005", @(12), @(15), @"123", @(PSTCKCardValidationStateValid)], + @[@"42424242", @(12), @(2023), @"123", @(PSTCKCardValidationStateIncomplete)], + @[@"378282246310005", @(12), @(2023), @"1234", @(PSTCKCardValidationStateInvalid)], @[@"378282246310005", @(12), @(15), @"12345", @(PSTCKCardValidationStateInvalid)], @[@"1234567812345678", @(12), @(15), @"12345", @(PSTCKCardValidationStateInvalid)], ]; @@ -231,7 +218,7 @@ - (void)testCardValidation { card.expYear = [test[2] integerValue]; card.cvc = test[3]; PSTCKCardValidationState state = [PSTCKCardValidator validationStateForCard:card - inCurrentYear:15 currentMonth:8]; + inCurrentYear:2021 currentMonth:8]; XCTAssertEqualObjects(@(state), test[4]); } } diff --git a/Tests/Tests/PSTCKCertTest.m b/Tests/Tests/PSTCKCertTest.m deleted file mode 100644 index 0612671..0000000 --- a/Tests/Tests/PSTCKCertTest.m +++ /dev/null @@ -1,73 +0,0 @@ -// -// PSTCKCertTest.m -// Paystack -// - -@import XCTest; - -#import "PSTCKAPIClient.h" -#import "PSTCKAPIClient+Private.h" -#import "Paystack.h" - -NSString *const PSTCKExamplePublishableKey = @"bad_key"; - -@interface PSTCKAPIClient (Failure) -@property (nonatomic, readwrite) NSURL *apiURL; -@end - -@interface PSTCKCertTest : XCTestCase -@end - -@implementation PSTCKCertTest - -- (void)testNoError { - XCTestExpectation *expectation = [self expectationWithDescription:@"Token creation"]; - PSTCKAPIClient *client = [[PSTCKAPIClient alloc] initWithPublishableKey:PSTCKExamplePublishableKey]; - [client createTokenWithData:[NSData new] - completion:^(PSTCKToken *token, NSError *error) { - [expectation fulfill]; - // Note that this API request *will* fail, but it will return error - // messages from the server and not be blocked by local cert checks - XCTAssertNil(token, @"Expected no token"); - XCTAssertNotNil(error, @"Expected error"); - }]; - [self waitForExpectationsWithTimeout:60.0f handler:nil]; -} - -- (void)testExpired { - [self createTokenWithBaseURL:[NSURL URLWithString:@"https://testssl-expire.disig.sk/index.en.html"] - completion:^(PSTCKToken *token, NSError *error) { - XCTAssertNil(token, @"Token should be nil."); - XCTAssertEqualObjects(error.domain, @"NSURLErrorDomain", @"Error should be NSURLErrorDomain"); - XCTAssertNotNil(error.userInfo[@"NSURLErrorFailingURLPeerTrustErrorKey"], - @"There should be a secTustRef for Foundation HTTPS errors"); - }]; -} - -- (void)testMismatched { - [self createTokenWithBaseURL:[NSURL URLWithString:@"https://mismatched.paystack.com"] - completion:^(PSTCKToken *token, NSError *error) { - XCTAssertNil(token, @"Token should be nil."); - XCTAssertEqualObjects(error.domain, @"NSURLErrorDomain", @"Error should be NSURLErrorDomain"); - }]; -} - -// helper method -- (void)createTokenWithBaseURL:(NSURL *)baseURL completion:(PSTCKTokenCompletionBlock)completion { - XCTestExpectation *expectation = [self expectationWithDescription:@"Token creation"]; - PSTCKAPIClient *client = [[PSTCKAPIClient alloc] initWithPublishableKey:PSTCKExamplePublishableKey]; - client.apiURL = baseURL; - [client createTokenWithData:[NSData new] - completion:^(PSTCKToken *token, NSError *error) { - [expectation fulfill]; - completion(token, error); - }]; - - [self waitForExpectationsWithTimeout:10.0f handler:nil]; -} - -@end - -@implementation PSTCKAPIClient (Failure) -@dynamic apiURL; -@end diff --git a/Tests/Tests/PSTCKFormEncoderTest.m b/Tests/Tests/PSTCKFormEncoderTest.m deleted file mode 100644 index 9444b3f..0000000 --- a/Tests/Tests/PSTCKFormEncoderTest.m +++ /dev/null @@ -1,109 +0,0 @@ -// -// PSTCKFormEncoderTest.m -// Paystack Tests -// - -@import XCTest; -#import "PSTCKFormEncoder.h" -#import "PSTCKFormEncodable.h" - -@interface PSTCKTestFormEncodableObject : NSObject -@property(nonatomic) NSString *testProperty; -@property(nonatomic) NSString *testIgnoredProperty; -@property(nonatomic) NSArray *testArrayProperty; -@property(nonatomic) NSDictionary *testDictionaryProperty; -@property(nonatomic) PSTCKTestFormEncodableObject *testNestedObjectProperty; -@end - -@implementation PSTCKTestFormEncodableObject - -@synthesize additionalAPIParameters; - -+ (NSString *)rootObjectName { - return @"test_object"; -} - -+ (NSDictionary *)propertyNamesToFormFieldNamesMapping { - return @{ - @"testProperty": @"test_property", - @"testArrayProperty": @"test_array_property", - @"testDictionaryProperty": @"test_dictionary_property", - @"testNestedObjectProperty": @"test_nested_property", - }; -} - -@end - -@interface PSTCKFormEncoderTest : XCTestCase -@end - -@implementation PSTCKFormEncoderTest - -- (void)testStringByReplacingSnakeCaseWithCamelCase { - NSString *camelCase = [PSTCKFormEncoder stringByReplacingSnakeCaseWithCamelCase:@"test_1_2_34_test"]; - XCTAssertEqualObjects(@"test1234Test", camelCase); -} - -// helper test method -- (NSString *)encodeObject:(PSTCKTestFormEncodableObject *)object { - NSData *encoded = [PSTCKFormEncoder formEncryptedDataForCard:object]; - return [[[NSString alloc] initWithData:encoded encoding:NSUTF8StringEncoding] stringByRemovingPercentEncoding]; -} - -- (void)testFormEncoding_emptyObject { - PSTCKTestFormEncodableObject *testObject = [PSTCKTestFormEncodableObject new]; - XCTAssertEqualObjects([self encodeObject:testObject], @""); -} - -- (void)testFormEncoding_normalObject { - PSTCKTestFormEncodableObject *testObject = [PSTCKTestFormEncodableObject new]; - testObject.testProperty = @"success"; - testObject.testIgnoredProperty = @"ignoreme"; - XCTAssertEqualObjects([self encodeObject:testObject], @"test_object[test_property]=success"); -} - -- (void)testFormEncoding_additionalAttributes { - PSTCKTestFormEncodableObject *testObject = [PSTCKTestFormEncodableObject new]; - testObject.testProperty = @"success"; - testObject.additionalAPIParameters = @{@"foo": @"bar", @"nested": @{@"nested_key": @"nested_value"}}; - XCTAssertEqualObjects([self encodeObject:testObject], @"test_object[foo]=bar&test_object[nested][nested_key]=nested_value&test_object[test_property]=success"); -} - -- (void)testFormEncoding_arrayValue_empty { - PSTCKTestFormEncodableObject *testObject = [PSTCKTestFormEncodableObject new]; - testObject.testProperty = @"success"; - testObject.testArrayProperty = @[]; - XCTAssertEqualObjects([self encodeObject:testObject], @"test_object[test_property]=success"); -} - -- (void)testFormEncoding_arrayValue { - PSTCKTestFormEncodableObject *testObject = [PSTCKTestFormEncodableObject new]; - testObject.testProperty = @"success"; - testObject.testArrayProperty = @[@1, @2, @3]; - XCTAssertEqualObjects([self encodeObject:testObject], @"test_object[test_array_property][]=1&test_object[test_array_property][]=2&test_object[test_array_property][]=3&test_object[test_property]=success"); -} - -- (void)testFormEncoding_dictionaryValue_empty { - PSTCKTestFormEncodableObject *testObject = [PSTCKTestFormEncodableObject new]; - testObject.testProperty = @"success"; - testObject.testDictionaryProperty = @{}; - XCTAssertEqualObjects([self encodeObject:testObject], @"test_object[test_property]=success"); -} - -- (void)testFormEncoding_dictionaryValue { - PSTCKTestFormEncodableObject *testObject = [PSTCKTestFormEncodableObject new]; - testObject.testProperty = @"success"; - testObject.testDictionaryProperty = @{@"foo": @"bar"}; - XCTAssertEqualObjects([self encodeObject:testObject], @"test_object[test_dictionary_property][foo]=bar&test_object[test_property]=success"); -} - -- (void)testFormEncoding_nestedValue { - PSTCKTestFormEncodableObject *testObject1 = [PSTCKTestFormEncodableObject new]; - PSTCKTestFormEncodableObject *testObject2 = [PSTCKTestFormEncodableObject new]; - testObject2.testProperty = @"nested_object"; - testObject1.testProperty = @"success"; - testObject1.testNestedObjectProperty = testObject2; - XCTAssertEqualObjects([self encodeObject:testObject1], @"test_object[test_nested_property][test_property]=nested_object&test_object[test_property]=success"); -} - -@end diff --git a/Tests/Tests/PSTCKPaymentCardTextFieldTest.m b/Tests/Tests/PSTCKPaymentCardTextFieldTest.m index c4f7def..a0d4761 100644 --- a/Tests/Tests/PSTCKPaymentCardTextFieldTest.m +++ b/Tests/Tests/PSTCKPaymentCardTextFieldTest.m @@ -26,26 +26,6 @@ @interface PSTCKPaymentCardTextFieldTest : XCTestCase @implementation PSTCKPaymentCardTextFieldTest -- (void)testIntrinsicContentSize { - PSTCKPaymentCardTextField *textField = [PSTCKPaymentCardTextField new]; - - UIFont *iOS8SystemFont = [UIFont fontWithName:@"HelveticaNeue" size:18]; - textField.font = iOS8SystemFont; - XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.height, 44, 0.1); - XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.width, 257, 0.1); - - UIFont *iOS9SystemFont = [UIFont fontWithName:@".SFUIText-Regular" size:18]; - if (iOS9SystemFont) { - textField.font = iOS9SystemFont; - XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.height, 44, 0.1); - XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.width, 270, 0.1); - } - - textField.font = [UIFont fontWithName:@"Avenir" size:44]; - XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.height, 60, 0.1); - XCTAssertEqualWithAccuracy(textField.intrinsicContentSize.width, 488, 0.1); -} - - (void)testSetCard_numberUnknown { PSTCKPaymentCardTextField *sut = [PSTCKPaymentCardTextField new]; PSTCKCardParams *card = [PSTCKCardParams new]; @@ -165,11 +145,6 @@ - (void)testSetCard_numberAndCVC { card.number = number; card.cvc = cvc; [sut setCardParams:card]; - NSData *imgData = UIImagePNGRepresentation(sut.brandImageView.image); - NSData *expectedImgData = UIImagePNGRepresentation([PSTCKPaymentCardTextField brandImageForCardBrand:PSTCKCardBrandAmex]); - - XCTAssertTrue(sut.numberFieldShrunk); - XCTAssertTrue([expectedImgData isEqualToData:imgData]); XCTAssertEqualObjects(sut.numberField.text, number); XCTAssertEqual(sut.expirationField.text.length, (NSUInteger)0); XCTAssertEqualObjects(sut.cvcField.text, cvc); @@ -185,11 +160,7 @@ - (void)testSetCard_expirationAndCVC { card.expYear = 99; card.cvc = cvc; [sut setCardParams:card]; - NSData *imgData = UIImagePNGRepresentation(sut.brandImageView.image); - NSData *expectedImgData = UIImagePNGRepresentation([PSTCKPaymentCardTextField brandImageForCardBrand:PSTCKCardBrandUnknown]); - XCTAssertFalse(sut.numberFieldShrunk); - XCTAssertTrue([expectedImgData isEqualToData:imgData]); XCTAssertEqual(sut.numberField.text.length, (NSUInteger)0); XCTAssertEqualObjects(sut.expirationField.text, @"10/99"); XCTAssertEqualObjects(sut.cvcField.text, cvc); diff --git a/Tests/Tests/PSTCKPaymentCardTextFieldViewModelTest.m b/Tests/Tests/PSTCKPaymentCardTextFieldViewModelTest.m index ae930ae..a2daccd 100644 --- a/Tests/Tests/PSTCKPaymentCardTextFieldViewModelTest.m +++ b/Tests/Tests/PSTCKPaymentCardTextFieldViewModelTest.m @@ -26,7 +26,7 @@ - (void)testCardNumber { @[@"4242424242424242", @"4242424242424242"], @[@"4242 4242 4242 4242", @"4242424242424242"], @[@"4242xxx4242", @"42424242"], - @[@"12345678901234567890", @"1234567890123456"], + @[@"12345678901234567890", @"12345678901234567890"], ]; for (NSArray *test in tests) { self.viewModel.cardNumber = test[0]; @@ -76,10 +76,10 @@ - (void)testNumberWithoutLastDigits { XCTAssertEqualObjects([self.viewModel numberWithoutLastDigits], @"424242424242"); self.viewModel.cardNumber = @"378282246310005"; - XCTAssertEqualObjects([self.viewModel numberWithoutLastDigits], @"3782822463"); + XCTAssertEqualObjects([self.viewModel numberWithoutLastDigits], @"37828224631"); self.viewModel.cardNumber = @""; - XCTAssertEqualObjects([self.viewModel numberWithoutLastDigits], @"123456781234"); + XCTAssertEqualObjects([self.viewModel numberWithoutLastDigits], @"1234 5678 1234 5678"); } - (void)testValidity { diff --git a/Tests/Tests/PSTCKRSATest.m b/Tests/Tests/PSTCKRSATest.m deleted file mode 100644 index 63176ab..0000000 --- a/Tests/Tests/PSTCKRSATest.m +++ /dev/null @@ -1,43 +0,0 @@ -// -// PSTCKRSATest.m -// Paystack -// -// Created by Ibrahim Lawal on Feb/27/2016. -// Copyright © 2016 Paystack, Inc. All rights reserved. -// - -#import -#import "PSTCKRSA.h" - -@interface PSTCKRSATest : XCTestCase - -@end - -@implementation PSTCKRSATest - -- (void)setUp { - [super setUp]; - // Put setup code here. This method is called before the invocation of each test method in the class. -} - -- (void)tearDown { - // Put teardown code here. This method is called after the invocation of each test method in the class. - [super tearDown]; -} - -- (void)testEncryptRSA { - NSString *encrypted = [PSTCKRSA encryptRSA:@"4123450131001381*883*08*18"]; -// NSLog(@"@%@",encrypted); - // we are fine with getting any value at all - XCTAssertNotNil(encrypted); -} - - -- (void)testPerformanceExample { - // Test the performance of our RSA encryption . - [self measureBlock:^{ - [self testEncryptRSA]; - }]; -} - -@end diff --git a/VERSION b/VERSION index 7dea76e..eea30e5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.1 +3.0.13 diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000..1803063 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,6 @@ +# app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app +# apple_id("[[APPLE_ID]]") # Your Apple email address + + +# For more information about the Appfile, see: +# https://docs.fastlane.tools/advanced/#appfile diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000..a6e4587 --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,60 @@ +# This file contains the fastlane.tools configuration +# You can find the documentation at https://docs.fastlane.tools +# +# For a list of all available actions, check out +# +# https://docs.fastlane.tools/actions +# +# For a list of all available plugins, check out +# +# https://docs.fastlane.tools/plugins/available-plugins +# + +# Uncomment the line if you want fastlane to automatically update itself +# update_fastlane + +default_platform(:ios) + +platform :ios do + desc "Description of what the lane does" + lane :custom_lane do + # add actions here: https://docs.fastlane.tools/actions + end + +desc "Run unit tests" + lane :unit_tests do + clear_derived_data + scan(scheme: "PaystackiOS Tests", + derived_data_path: "temp", + clean: true + ) + end + +desc "Sonar Cloud Scanner" + lane :metrics do + + scan(scheme: "PaystackiOS Tests", + code_coverage: true, + output_directory: "./sonar-reports", + devices: "iPhone 8") + + slather(sonarqube_xml: true, + scheme: "PaystackiOS", + proj: "./Paystack.xcodeproj", + output_directory: "./code-coverage", + workspace: "./Paystack.xcworkspace") + + swiftlint(output_file: "./sonar-reports/swiftlint.txt", + ignore_exit_status: true) + + sonar( + project_key: "PaystackHQ_paystack-ios", + project_version: "1.0", + project_name: "PaystackiOS", + sources_path: File.expand_path("../Paystack"), + sonar_organization: "paystackhq", + sonar_login: ENV["SONAR_TOKEN"], + sonar_url: "https://sonarcloud.io" + ) + end +end diff --git a/fastlane/README.md b/fastlane/README.md new file mode 100644 index 0000000..ade99ca --- /dev/null +++ b/fastlane/README.md @@ -0,0 +1,39 @@ +fastlane documentation +================ +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +``` +xcode-select --install +``` + +Install _fastlane_ using +``` +[sudo] gem install fastlane -NV +``` +or alternatively using `brew install fastlane` + +# Available Actions +## iOS +### ios custom_lane +``` +fastlane ios custom_lane +``` +Description of what the lane does +### ios unit_tests +``` +fastlane ios unit_tests +``` +Run unit tests +### ios metrics +``` +fastlane ios metrics +``` +Sonar Cloud Scanner + +---- + +This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..006ee04 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,12 @@ +sonar.projectKey=PaystackHQ_paystack-ios +sonar.organization=paystackhq +sonar.projectName=paystack-ios +sonar.projectVersion=1.0 +sonar.cfamily.build-wrapper-output=DerivedDatacompilation-database +sonar.c.file.suffixes=- +sonar.objc.file.suffixes=.h,.m +sonar.sourceEncoding=UTF-8 +sonar.cfamily.threads=1 +sonar.cfamily.cache.enabled=false +sonar.sources=\Paystack +sonar.coverageReportPaths=code-coverage/sonarqube-generic-coverage.xml