Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
=== 3.1.0 2026-10-21

* This SDK is deprecated in favour of https://github.com/PaystackHQ/paystack-sdk-ios. All deprecation messages, including PSTCKCard.fingerprint and the PSTCKCard setters, now point to the new SDK. They remain warnings, not errors
* CocoaPods trunk stops accepting new podspecs on 2 December 2026 (https://blog.cocoapods.org/CocoaPods-Specs-Repo/). Existing versions stay installable, but no new versions of this pod can be published after that date; move to the new SDK for future updates
* Requires iOS 15.0+ and Xcode 26 or later. Apps built with the current iOS SDK must adopt the UIScene life cycle (the Example app shows how)
* Example app no longer needs a backend: set paystackPublicKey and accessCode in ViewController.swift
* Raise minimum iOS deployment target to 15.0 (SDK, tests, Example and podspec); required to build with current Xcode
* Stop treating compiler warnings as errors in the iOS SDK and test targets so the deprecated Security padding APIs in PSTCKRSA compile
* Requery now uses charge/requery/{access_code}. The access code from PSTCKTransactionParams.access_code is stored for the whole charge
* Charges started without an access code temporarily fall back to charge/requery/{trans}; this will be removed in a future release, so initialize transactions on your server (transaction/initialize) and pass access_code
* Requery polls every 5s while the status is pending, up to 12 times (60s), then ends with an error; success, failed and error statuses end the charge immediately
* Authorization: Bearer <public key> and Paystack-Version (the SDK version, e.g. 3.1.0) are now set on every request, including the address verification states lookup; changing publicKey after init now takes effect on the next request
* A second chargeCard call while a charge is in progress no longer overwrites the running charge's callbacks; optional callbacks are reset per charge
* Fix crashes when requerying without a transaction id and when a response body is not valid UTF-8
* fetchStates now URL-encodes the country and always calls its completion

=== 3.0.13 2019-11-4

* Updates deprecated class UIWebView to WKWebView
Expand Down
8 changes: 4 additions & 4 deletions Example/Paystack iOS Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,9 @@
);
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
INFOPLIST_FILE = "$(SRCROOT)/Paystack iOS Example/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 3.0.13;
MARKETING_VERSION = 3.1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "Paystack iOS Example";
PROVISIONING_PROFILE = "";
Expand All @@ -431,9 +431,9 @@
);
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
INFOPLIST_FILE = "$(SRCROOT)/Paystack iOS Example/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MARKETING_VERSION = 3.0.13;
MARKETING_VERSION = 3.1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "Paystack iOS Example";
PROVISIONING_PROFILE = "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All @@ -36,8 +34,6 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
22 changes: 20 additions & 2 deletions Example/Paystack iOS Example/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,31 @@ extension String {
}
}

@UIApplicationMain
@main
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}

// MARK: UISceneSession lifecycle

func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Matches the "Default Configuration" entry in Info.plist's UIApplicationSceneManifest.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
}

/// Owns the window now that the app uses the UIScene life cycle (required by the current iOS SDK).
/// Info.plist points UISceneStoryboardFile at Main.storyboard, so UIKit creates the window and
/// loads the initial view controller; nothing else is needed here.
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard scene is UIWindowScene else { return }
}
}
23 changes: 20 additions & 3 deletions Example/Paystack iOS Example/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,28 @@
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
Expand Down
94 changes: 20 additions & 74 deletions Example/Paystack iOS Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ import Paystack
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 paystackPublicKey = ""
// let paystackPublicKey = "pk_live_ed8a7004bb85bf636a0a22c635ad9d1788caa5de"

// To set this up, see https://github.com/PaystackHQ/sample-charge-card-backend
let backendURLString = ""
// let backendURLString = "https://calm-scrubland-33409.herokuapp.com"
// This sample runs without a backend: it charges with a hard-coded public key and access code.
// Find your public key at https://dashboard.paystack.co/#/settings/developer
// set Public key here
let paystackPublicKey = "public_key"

// set access code here
// Get one from a transaction/initialize call (response `data.access_code`) made with your secret key,
// in the same mode (test/live) as the public key. Access codes are single-use: initialize a new
// transaction for each test charge.
let accessCode = "access_code"

let card : PSTCKCard = PSTCKCard()

Expand Down Expand Up @@ -74,13 +76,12 @@ class ViewController: UIViewController, PSTCKPaymentCardTextFieldDelegate {

if cardDetailsForm.isValid {

if backendURLString != "" {
fetchAccessCodeAndChargeCard()
// Make sure the access code has been set
if accessCode == "" {
showOkayableMessage("You need to set an access code.", message:"Initialize a transaction with your secret key and paste data.access_code into accessCode in ViewController.swift.")
return
}
showOkayableMessage("Backend not configured", message:"To run this sample, please configure your backend.")
// chargeWithSDK(newCode:"");

chargeWithSDK(accessCode: accessCode)
}

}
Expand All @@ -95,33 +96,10 @@ class ViewController: UIViewController, PSTCKPaymentCardTextFieldDelegate {
}
}

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){
func chargeWithSDK(accessCode: String){
outputOnLabel(str: "Charging with access code " + accessCode)
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)
// }
transactionParams.access_code = accessCode;
// 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")
Expand All @@ -140,7 +118,6 @@ class ViewController: UIViewController, PSTCKPaymentCardTextFieldDelegate {
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)
Expand All @@ -160,41 +137,10 @@ class ViewController: UIViewController, PSTCKPaymentCardTextFieldDelegate {
}) { (reference) in
self.outputOnLabel(str: "succeeded: " + reference)
self.chargeCardButton.isEnabled = true;
self.verifyTransaction(reference: reference)
// Verify on your server before giving value:
// GET https://api.paystack.co/transaction/verify/<reference> with your secret key.
self.outputOnLabel(str: "verify " + reference + " on your server (transaction/verify)")
}
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: "<Unable to read response> while "+message)
print("<Unable to read response>")
}
} 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()
}
}
12 changes: 12 additions & 0 deletions GUIDE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Guide

> **This SDK is deprecated.** New integrations should use the new Paystack iOS SDK: https://github.com/PaystackHQ/paystack-sdk-ios.
>
> **CocoaPods trunk stops accepting new versions on 2 December 2026** ([announcement](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)).
> Existing versions of this pod stay installable, but no new versions can be published after that date.
>
> Requirements: iOS 15.0+, Xcode 26 or later, Keychain Sharing entitlement. Always pass an `access_code` from your server's
> `transaction/initialize` call: charges without one will stop working in a future release.

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 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.
Expand Down Expand Up @@ -47,6 +55,10 @@ We recommend using [CocoaPods](https://cocoapods.org/) to install the Paystack i

If you haven't set up Cocoapods before, their site has installation instructions. Then, add pod 'Paystack' to your Podfile, and run pod install.

> **Note:** CocoaPods trunk becomes read-only on 2 December 2026 ([announcement](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)).
> `pod install` keeps working for versions already published, but this pod won't receive new versions through CocoaPods after
> that date. Plan your move to the [new SDK](https://github.com/PaystackHQ/paystack-sdk-ios), which supports Swift Package Manager.

(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
Expand Down
4 changes: 2 additions & 2 deletions Paystack.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'Paystack'
s.version = '3.0.17'
s.version = '3.1.0'
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.
Expand All @@ -14,7 +14,7 @@ Pod::Spec.new do |s|
s.ios.weak_frameworks = 'PassKit', 'AddressBook'
s.requires_arc = true
s.default_subspecs = 'Core'
s.ios.deployment_target = '11.0'
s.ios.deployment_target = '15.0'
s.swift_versions = '5.0'


Expand Down
12 changes: 8 additions & 4 deletions Paystack.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
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 */; };
A1B2C3D4E5F6071829304A5C /* PSTCKChargeRequeryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F6071829304A5B /* PSTCKChargeRequeryTest.m */; };
04415C701A6605B5001225ED /* PSTCKTokenTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 04CDB5271A5F3A9300B854EE /* PSTCKTokenTest.m */; };
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 */; };
Expand Down Expand Up @@ -402,6 +403,7 @@
04CDB4CE1A5F30A700B854EE /* PaystackError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PaystackError.h; path = PublicHeaders/PaystackError.h; sourceTree = "<group>"; };
04CDB4CF1A5F30A700B854EE /* PaystackError.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PaystackError.m; sourceTree = "<group>"; };
04CDB51E1A5F3A9300B854EE /* PSTCKAPIClientTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKAPIClientTest.m; sourceTree = "<group>"; };
A1B2C3D4E5F6071829304A5B /* PSTCKChargeRequeryTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKChargeRequeryTest.m; sourceTree = "<group>"; };
04CDB5271A5F3A9300B854EE /* PSTCKTokenTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKTokenTest.m; sourceTree = "<group>"; };
04CDE5B41BC1F1F100548833 /* PSTCKCardParams.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PSTCKCardParams.m; sourceTree = "<group>"; };
04CDE5BB1BC1F21500548833 /* PSTCKCardParams.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PSTCKCardParams.h; path = PublicHeaders/PSTCKCardParams.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -644,6 +646,7 @@
isa = PBXGroup;
children = (
04CDB51E1A5F3A9300B854EE /* PSTCKAPIClientTest.m */,
A1B2C3D4E5F6071829304A5B /* PSTCKChargeRequeryTest.m */,
0438EF4A1B741B0100D506CC /* PSTCKCardValidatorTest.m */,
045A62AA1B8E7259000165CE /* PSTCKPaymentCardTextFieldTest.m */,
0438EF4B1B741B0100D506CC /* PSTCKPaymentCardTextFieldViewModelTest.m */,
Expand Down Expand Up @@ -1176,6 +1179,7 @@
08C260AF1E9C214A002AE28C /* PSTCKAuthViewController.m in Sources */,
04415C661A6605B5001225ED /* PaystackError.m in Sources */,
04415C671A6605B5001225ED /* PSTCKAPIClientTest.m in Sources */,
A1B2C3D4E5F6071829304A5C /* PSTCKChargeRequeryTest.m in Sources */,
0438EF361B7416BB00D506CC /* PSTCKPaymentCardTextField.m in Sources */,
04415C701A6605B5001225ED /* PSTCKTokenTest.m in Sources */,
);
Expand Down Expand Up @@ -1353,9 +1357,9 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 3.0.17;
MARKETING_VERSION = 3.1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.paystack-ios";
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
Expand All @@ -1372,9 +1376,9 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 3.0.17;
MARKETING_VERSION = 3.1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.paystack.paystack-ios";
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_VERSION = 5.0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR


IPHONEOS_DEPLOYMENT_TARGET = 8.0
IPHONEOS_DEPLOYMENT_TARGET = 15.0


// Code Signing Identity
Expand All @@ -43,7 +43,7 @@ CODE_SIGN_IDENTITY = iPhone Developer
FRAMEWORK_SEARCH_PATHS = $(inherited)


GCC_TREAT_WARNINGS_AS_ERRORS = YES
GCC_TREAT_WARNINGS_AS_ERRORS = NO


GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR
Expand Down
2 changes: 1 addition & 1 deletion Paystack/BuildConfigurations/PaystackiOS-Shared.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ DYLIB_CURRENT_VERSION = 1
DYLIB_INSTALL_NAME_BASE = @rpath


GCC_TREAT_WARNINGS_AS_ERRORS = YES
GCC_TREAT_WARNINGS_AS_ERRORS = NO


GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR
Expand Down
Loading
Loading