1 - Project Set up
First, you need a Terminal3 account. Register here.
This integration requires your Terminal3 project credentials:
- Project Key: Your public project identifier
- Secret Key: Your private project key
Your payment methods are configured through your Terminal3 Dashboard. Ensure you have enabled the payment methods you want to support:
- Brick: Credit and debit cards
- PayAlto: Local payment methods based on user location
2 - Core SDK Integration Instruction
Step 1: Add Core SDK to the project
Install with Swift Package Manager
Location: https://github.com/paymentwall/ios-gamepay-sdk
Install with Cocoapods
pod 'GamePaySDK', :git => 'https://github.com/paymentwall/ios-gamepay-sdk', :tag => '<sdk_version>'
Install as a manual framework
- Download the
xcframework
file from GitHub and move it to the project folder - Open Project settings → choose
General
tab → chooseTarget
→ Dragxcframework
file intoFrameworks and Libraries
section → chooseEmbed & sign

Step 2: Add payment Methods
You can choose one or more payment methods according to your needs.
- Brick
- PayAlto
Init the payment method instances and PaymentSheet.Configuration
instance
let paymentOptions: [PaymentOption] = [brickOption, payAltoOption]
let configuration = PaymentSheet.Configuration(paymentOptions: paymentOptions)
You need to establish your display name, terms of service URL, and privacy policy URL, ensuring that users can access them.
configuration.merchantDisplayName = "Demo Merchant"
configuration.merchantTermsOfServiceURL = "https://example.com/terms-of-service"
configuration.merchantPrivacyPolicyURL = "https://example.com/privacy-policy"
Step 3: Create a new payment
let payment = PaymentObject(
itemID: "testuid",
userID: "testuid",
name: "Test",
price: 0.99,
currency: "USD",
image: nil
)
Step 4: Present PaymentSheet
Init PaymentSheet
and present
class ViewController: UIViewController {
var paymentSheet: PaymentSheet?
func checkout() {
// Setup payment methods
let paymentOptions: [PaymentOption] = [brickOption, payAltoOption]
let configuration = PaymentSheet.Configuration(paymentOptions: paymentOptions)
configuration.merchantDisplayName = "Demo Merchant"
configuration.environment = .production
let payment = PaymentObject(
itemID: "testuid",
userID: "testuid",
name: "Test",
price: 0.99,
currency: "USD",
image: nil
)
paymentSheet = PaymentSheet(payment: payment, configuration: configuration)
paymentSheet?.present(from: self, delegate: self)
}
}
You can define global keys for all options as follows:
configuration.setKeys(projectKey: "your-project-key", secretKey: "your-secret-key")
Step 5: Handle Payment Result
Implement PaymentSheetDelegate
protocol to handle payment result:
extension ViewController: PaymentSheetDelegate {
func handlePaymentResult(_ result: PaymentSheetResult) {
let status: String
switch result.status {
case .completed:
status = "SUCCESS"
case .pending:
status = "PENDING"
case .canceled:
status = "CANCELED"
case .failed:
var errorMesssage: String
if let error = result.error,
let gpError = error as? GPAPIClientError {
errorMesssage = gpError.error
} else {
errorMesssage = result.error?.localizedDescription ?? ""
}
status = "ERROR: \(errorMesssage)"
@unknown default:
status = "UNKNOWN"
}
showAlert(title: "Payment Result",
message: status,
actionTitle: "Got it",
completeHandler: nil)
}
}
3 - Payment Methods Integration Details
3.1 - Brick
Brick sequence diagram
Step 1: Create BrickPaymentOption
let brickOption = BrickPaymentOption()
brickOption.setKeys(projectKey: {BRICK_PUBLIC_KEY},
secretKey: {BRICK_SECRET_KEY})
If a key is set for an individual option, it will override the global key
Step 2: Create A Charge
The SDK automatically retrieves the one-time token and returns it to your application. You can then use it in your backend to initiate a charge request to our server.
Client Side: Implement handleChargeRequest
from PaymentSheetDelegate
func handleChargeRequest(token: BrickOTTResponse, payment: PaymentObject, completionHandler: @escaping (Data) -> Void) {}
Server Side:
<?php
Paymentwall_Config::getInstance()->set(array(
'private_key' => 'YOUR_PRIVATE_KEY'
));
$parameters = $_POST;
$chargeInfo = array(
'email' => $parameters['email'],
'history[registration_date]' => '1489655092',
'amount' => 9.99,
'currency' => 'USD',
'token' => $parameters['brick_token'],
'fingerprint' => $parameters['brick_fingerprint'],
'description' => 'Order #123',
'secure' => '1',
'secure_return_method' => "url",
'secure_redirect_url' => $redirectUrl
);
$charge = new Paymentwall_Charge();
$charge->create($chargeInfo);
Parameters and description can be found here.
Step 3: Handle Charge Response
If the charge request is successful, pass the data to the completionHandler
so the SDK can process it
func handleChargeRequest(token: BrickOTTResponse, payment: PaymentObject, completionHandler: @escaping (Data) -> Void) {
var req = URLRequest(url: URL(string: "https://your-server-domain/charge")!)
let bodyDict: [String: Any] = ["token": token.token,
"email": token.email]
if let data = try? JSONSerialization.data(withJSONObject: bodyDict, options: []) {
req.httpBody = data
}
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: req, completionHandler: { (data, response, error) in
DispatchQueue.main.async {
if let data = data {
completionHandler(data)
}
}
}).resume()
}
If 3D Secure is necessary, the SDK will automatically show the 3D Secure page for the user to complete the challenge.
Displaying a 3D secure form for your customers is the first step. After that, brick_secure_token
and brick_charge_id
will be sent to secure_redirect_url
via POST each time a payer confirms the 3D secure payment step. You can now continue with the next step
<?php
$chargeInfo = array(
... // your original charge request parameters
);
if (isset($parameters['brick_charge_id']) AND isset($parameters['brick_secure_token'])) {
$chargeInfo['charge_id'] = $parameters['brick_charge_id'];
$chargeInfo['secure_token'] = $parameters['brick_secure_token'];
}
?>
Step 4: Handle payment result
Once the second charge is completed successfully, the SDK will send the payment result back to your application. You can reference the charge object using result.charge
3.2 - PayAlto
Step 1: Create PayAltoPaymentOption
let payAltoOption = PayAltoPaymentOption()
var payAltoSetting = PayAltoPaymentOption.Setting()
payAltoOption.setting = payAltoSetting
payAltoOption.setKeys(projectKey: {PAYALTO_PUBLIC_KEY},
secretKey: {PAYALTO_SECRET_KEY})
Step 2: Set up custom params. (Optional)
Set up custom params through PayAltoPaymentOption.Setting
var payAltoSetting = PayAltoPaymentOption.Setting()
payAltoSetting.successUrl = {SUCCESS_URL}
payAltoSetting.clientUrl = {CLIENT_URL}
payAltoSetting.countryCode = {USER_COUNTRY_CODE}
payAltoSetting.widget = {WIDGET_TYPE}
- success_url: URL of the page where the end-user should be redirected to after the payment is complete. Additionally,
$ref
can be added as a placeholder insuccess_url
to pass the transaction id. E.g. https://website.com/thank-you?transaction_id=$ref - client_url: Optional URL of pingback listener script where pingbacks should be sent. It overrides the default Pingback URL which is set up in Project Settings in Merchant Area. Please send a request to devsupport@paymentwall.com to activate this feature.
- country_code: The length must be 2-character according to ISO 3166-1 alpha-2 code of the country. Required to be in uppercase. It overrides the location detected by IP.
- widget: Widget code. It can be obtained in the widget sections of your projects.
3.3 - Full Integration
class ViewController: UIViewController {
var paymentSheet: PaymentSheet?
func checkout() {
let brickOption = BrickPaymentOption()
brickOption.setKeys(
projectKey: { BRICK_PUBLIC_KEY },
secretKey: { BRICK_SECRET_KEY })
let payAltoOption = PayAltoPaymentOption()
var payAltoSetting = PayAltoPaymentOption.Setting()
payAltoOption.setting = payAltoSetting
payAltoOption.setKeys(
projectKey: { PAYALTO_PUBLIC_KEY },
secretKey: { PAYALTO_SECRET_KEY })
let paymentOptions: [PaymentOption] = [brickOption, payAltoOption]
let configuration = PaymentSheet.Configuration(paymentOptions: paymentOptions)
configuration.merchantDisplayName = "Demo Merchant"
configuration.environment = .production
let payment = PaymentObject(
itemID: "testuid",
userID: "testuid",
name: "Test",
price: 0.99,
currency: "USD",
image: nil
)
paymentSheet = PaymentSheet(payment: payment, configuration: configuration)
paymentSheet?.present(from: self, delegate: self)
}
}
4 - Link out payment
In certain countries, it’s possible to link to an external website to process payments on iOS. For instance, this guide explains how to sell in-app credits by using GamePay Checkout, which directs users to a GamePay-hosted payment page for completing their purchase.
4.1 - Setup deeplink
Universal links enable Checkout to open your app directly. To set up a universal link:
- Host an
apple-app-site-association
file on your domain. - Add the Associated Domains entitlement to your app configuration.
Add an apple-app-site-association
file to your domain.
Place a file named apple-app-site-association
in the .well-known
directory of your domain to specify which URLs your app can handle. In this file, include your App ID, prefixed by your Team ID, which you can locate on the Membership page of the Apple Developer Portal.
{
"applinks": {
"details": [
{
"appIDs": [ "37N222R38X.com.example.app"],
"appID": "37N222R38X.com.example.app",
"components": [
{
"/": "/linkout-redirect*",
"comment": "This matches any URL with a path that begins with /linkout-redirect"
}
]
}
]
}
}
Add the Associated Domains entitlement to your app configuration.
- Open the Signing & Capabilities tab for your app’s target in Xcode.
- Click + Capability, then choose Associated Domains.
- Add
applinks:example.com
to the Associated Domains list.
For more details, refer to Apple’s Universal Links for Developers documentation.
4.2 - Create a LinkOutPaymentHandler
LinkOutPaymentHandler
is an object that manages redirection to the Terminal3 payment widget through an external browser. Configure it with the following:
- userId: The customer ID
- successUrl: A universal link or custom scheme that brings the user back to your app after payment.
- externalID: ID of your product. Order reference ID could also be set here. We will communicate back to you via the pingback as goodsid parameter. The maximum length is 256.
- countryCode (Optional): The length must be 2-character according to ISO 3166-1 alpha-2 code of the country. Required to be in uppercase. It overrides the location detected by IP.
For more details, refer to API Reference
Make sure your app is set up to handle the return URL properly, using universal links or a registered URL scheme.
let userID = UUID().uuidString
let externalID = UUID().uuidString
let setting = LinkOutPaymentHandler.WidgetSetting(projectKey: "your-project-key",
secretKey: "your-project-secret-key",
userId: userID,
successUrl: "https://example.com/linkout-redirect",
amount: 0.5,
currencyCode: "USD",
externalID: externalID,
countryCode: nil)
self.linkOutPaymentHandler = .init(with: setting)
4.3 - Start a checkout
Opens the checkout URL in Safari
self.linkOutPaymentHandler?.start { [weak self] in
self?.showSuccessAlert()
}
doPollingToGetPaymentStatus()
Resume the workflow once the payment is completed and the user is redirected back to the app
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
if URLCallBackCoordinator.handleReturnURL(url.absoluteString) {
return
}
}
4.4 - Start polling to check the payment status
Since transactions can occasionally take some time to complete and users might return to your application on their own, you can implement a polling request to regularly check the payment status and promptly show the result once it’s available.
private func doPollingToGetPaymentStatus() {
linkOutPaymentHandler?.getPaymentStatus { [weak self] payments in
guard let self else { return }
if payments.isEmpty { // keep polling
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.doPollingToGetPaymentStatus()
}
} else { // show success
showSuccessAlert()
}
}
}
4.5 - Show payment result to your user
Once the transaction is complete, you can show the result to the user
private func showSuccessAlert() {
guard !didHandlePaymentResult else { return }
setButtonState(true)
showAlert(title: "Transaction Result", message: "Success", completeHandler: nil)
didHandlePaymentResult = true
}
5 - Test the Integration
5.1 - Sandbox
Brick
Retrieve sandbox keys for Brick in the Merchant Dashboard.
Terminal3 provides you with a standard Visa test card to test payments:
- 4242 4242 4242 4242
- 4000 0000 0000 0002
You should use a valid expiration date to test.
CVV/CSC could be set to any 3 or 4-digit number to test payments normally. Using the CVV/VSC code below will result in a different outcome while performing test payments.
CVV/CSC | Description |
---|---|
111 | Error: Please ensure the CVV/CSC number is correct. |
222 | Error: Please contact your credit card issuing bank to check your balance. |
333 | Error: Please contact your credit card issuing bank to approve your payment. |
555 | Review: Your payment is under risk review and will be accept automatically after 2 mins |
556 | Review: Your payment is under risk review and will be declined automatically after 2 mis |
PayAlto
Follow the instructions to set up the Test Payment Method.
Next Steps
- Test thoroughly with all supported payment methods
- Set up webhook endpoints to handle payment notifications
- Implement proper error handling and user feedback
- Configure your Terminal3 Dashboard with appropriate settings
- Test in the production environment before going live
For additional support and advanced features, visit the Terminal3 Developer Portal.