A mobile application that processes payments must comply with the PCI DSS standard. Acquirers demand security confirmation, and code often contains vulnerabilities: PAN in logs, missing certificate pinning, ability to take screenshots of the screen with the card. Our team helps bring the app to the current version of the standard and pass a QSA audit with guaranteed compliance. With over 5 years of experience and 50+ mobile security projects, we ensure your app meets all requirements.
Why PCI DSS is Critical for Mobile Apps
Source: PCI DSS is the standard of the Payment Card Industry Security Standards Council, mandatory for any software that transmits, processes, or stores cardholder data. For mobile apps, Section 6 (Secure Development) and Section 4 (Encryption in Transit) are especially important. Without compliance, major acquirers do not allow direct acquiring—only iFrame solutions with limited functionality.
Typical Violations and How to Fix Them
Storing PAN in Logs
A developer adds Log.d("Payment", "Card: $cardNumber") for debugging. Data ends up in logcat, readable by any app on an unsecured Android. PCI DSS requirement 3.3: PAN must not appear in logs in plaintext. The correct solution is masking PAN and logging only operations without card data.
Lack of Screen Protection
Android by default allows screenshots of any Activity. On the card entry screen, this is a critical vulnerability:
// Must be on any screen with card data window.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE ) On iOS similarly, you need to cover the UI when entering background:
func applicationWillResignActive(_ application: UIApplication) { coverSensitiveContent() } Certificate Pinning Not Configured
Traffic to the payment server is intercepted by Charles Proxy or mitmproxy with no obstacles. PCI DSS 6.5.4 requires protection against man-in-the-middle attacks. Implementation on iOS:
class PinnedURLSessionDelegate: NSObject, URLSessionDelegate { func urlSession( _ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void ) { guard let serverTrust = challenge.protectionSpace.serverTrust, let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else { completionHandler(.cancelAuthenticationChallenge, nil) return } let pinnedHash = "sha256/AbCdEfGhIjKlMnOpQrStUvWxYz123456789=" let serverHash = certificateHash(certificate) if serverHash == pinnedHash { completionHandler(.useCredential, URLCredential(trust: serverTrust)) } else { completionHandler(.cancelAuthenticationChallenge, nil) } } } Root/Jailbreak Detection
PCI DSS 6.4.3 recommends detecting compromised devices. We use a combination: check for /bin/bash, Cydia, su command, process signature mismatch. Major acquirers require this via SDK.
Why Tokenization Is the Key Tool for PCI DSS?
The main principle is to never store PAN at all. The app works with a token from the payment provider: Stripe PaymentMethod, CloudPayments Token, YooKassa payment_method_id. Tokenization reduces PCI DSS scope from SAQ D to SAQ A—that's 10 times simpler and 20 times less audit effort. Fines for PAN leakage can reach $500,000 per quarter, and savings on audit due to correct scoping can be up to $3,000 (or more).
import StripePaymentSheet var configuration = PaymentSheet.Configuration() configuration.merchantDisplayName = "YourShop" PaymentSheet.FlowController.create( paymentIntentClientSecret: clientSecret, configuration: configuration ) { [weak self] result in switch result { case .success(let flowController): self?.paymentSheetFlowController = flowController case .failure(let error): // handle } } Card data goes directly to the Stripe SDK; the app receives paymentMethodId. PAN never touches your server.
Audit Trail and Logging
PCI DSS requirement 10: log access to the cardholder data environment. For mobile apps:
- Log every successful/unsuccessful payment with timestamp, userId, masked PAN (first 6 + last 4 digits), status.
- Store logs for at least 12 months, with 3 months immediately accessible.
- Protect logs from modification (append-only storage).
Masking PAN in logs:
fun maskPan(pan: String): String { return pan.take(6) + "*".repeat(pan.length - 10) + pan.takeLast(4) } // "4111111111111111" → "411111******1111" What Is Scoping and Why Is It Needed?
The choice of SAQ type directly affects the audit volume. Comparison of SAQ A and SAQ D:
| Parameter | SAQ A | SAQ D |
|---|---|---|
| Scope | Only tokenization | Full CDE |
| Number of requirements | 13 | 250+ |
| Audit effort | 2-3 days | 3-4 weeks |
| Remediation cost | Low | High |
Proper tokenization and outsourcing card processing (Stripe, CloudPayments) allow you to qualify for SAQ A. The savings on audit can exceed the cost of modifications. Click to expand: SAQ A is better than SAQ D by 20x in effort and 10x in cost.
This means your team can focus on features instead of compliance overhead.
Our Process: 5 Steps to Compliance
| Step | Duration | Result |
|---|---|---|
| Scoping | 1 day | CDE boundaries defined, SAQ type selected |
| Gap analysis | 2-3 days | List of non-conformities against 12 requirements |
| Remediation | 1-3 weeks | Fix vulnerabilities: certificate pinning, FLAG_SECURE, encryption of local storage |
| Penetration testing | 3-5 days | Static (MobSF) and dynamic analysis, binary reversing check |
| Preparation for QSA audit | 2 days | Documentation, reports, recommendations |
Step-by-Step Certificate Pinning on iOS
- Obtain the server certificate (DER).
- Compute SHA-256 hash of the certificate.
- Create
URLSessionDelegatewith hash check. - Embed the hash as a constant in code.
- For each request, compare server hash with the embedded one.
- On mismatch, terminate the connection.
This sequence guarantees protection against MITM attacks. On Android, certificate pinning is easier via NetworkSecurityConfig. Just add XML config in res/xml/network_security_config.xml with a list of pin certificates. However, for more flexibility we use OkHttp with CertificatePinner.
What's Included
- Full gap analysis of codebase and infrastructure (identify all non-conformities)
- Implement certificate pinning on iOS and Android with version and config handling
- Protect card entry screens (FLAG_SECURE, overlay prevention)
- Set up tokenization via Stripe/CloudPayments
- Implement root/jailbreak detection with multiple checks
- Audit logs and implement PAN masking
- Consultation on SAQ and passing QSA audit
Estimated Timeline
Audit and basic fixes: from 2 to 4 weeks. Full cycle with penetration testing and QSA preparation: up to 6 weeks. Cost is calculated individually after analyzing the current state.
Order a preliminary audit—we will assess the scope and timeline in 2 days. Contact us for a consultation, and we will help prepare your app for certification. We guarantee compliance with the standard and successful external audit.
Get your app evaluated in 2 days—reach out to us.







