• Stars
    star
    762
  • Rank 57,272 (Top 2 %)
  • Language
    Swift
  • License
    MIT License
  • Created about 8 years ago
  • Updated over 4 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Payment Card UI & Validator for iOS

Caishen

Travis build status Cocoapods Compatible Carthage compatible Platform Docs

Description

Caishen provides an easy-to-use text field to ask users for payment card information and to validate the input. It serves a similar purpose as PaymentKit, but is developed as a standalone framework entirely written in Swift. Caishen also allows an easy integration with other third-party frameworks, such as CardIO.

![Caishen example](caishen_example.gif)

Requirements

  • iOS 8.0+
  • Xcode 10.0+

Installation

CocoaPods

Caishen is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod "Caishen"

Carthage

Caishen is available through Carthage. To install it, simply add the following line to your Cartfile:

github "prolificinteractive/Caishen"

Usage

Example

To run the example project, clone the repo, and run pod install from the Example directory first.


Inside your project

To add a text field for entering card information to a view, either ...

  • ... add a UITextField to your view in InterfaceBuilder and change its class to CardTextField (when using InterfaceBuilder)
  • ... or initiate a CardTextField with one of its initializers (when instantiating from code):
    • init?(coder: aDecoder: NSCoder)
    • init(frame: CGRect)

To get updates about entered card information on your view controller, confirm to the protocol CardTextFieldDelegate set the view controller as cardTextFieldDelegate for the text field:

class MyViewController: UIViewController, CardTextFieldDelegate {
	
	@IBOutlet weak var cardTextField: CardTextField?
	
	override func viewDidLoad() {
		cardTextField?.cardTextFieldDelegate = self
		
		...
	}
	
	func cardTextField(_ cardTextField: CardTextField, didEnterCardInformation information: Card, withValidationResult validationResult: CardValidationResult) {
        // A valid card has been entered, if validationResult == CardValidationResult.Valid
    }
    
    func cardTextFieldShouldShowAccessoryImage(_ cardTextField: CardTextField) -> UIImage? {
        // You can return an image which will be used on cardTextField's accessory button
		 // If you return nil but provide an accessory button action, the unicode character "⇤" is displayed instead of an image to indicate an action that affects the text field.
    }
    
    func cardTextFieldShouldProvideAccessoryAction(_ cardTextField: CardTextField) -> (() -> ())? {
		 // You can return a callback function which will be called if a user tapped on cardTextField's accessory button
		 // If you return nil, cardTextField won't display an accessory button at all.
    }
	
	...

Customizing the text field appearance

CardTextField is mostly customizable like every other UITextField. Setting any of the following standard attributes for a CardTextField (either from code or from interface builder) will affect the text field just like it affects any other UITextField:

Property Type Description
placeholder String? The card number place holder. When using a card number as placeholder, make sure to format it appropriately so it uses the cardNumberSeparator that has been set for the text field (i.e. when using " - " as separator, set a placeholder like "1234 - 1234 - 1234 - 1234").
textColor UIColor? The color of text entered into the CardTextField.
backgroundColor UIColor? The background color of the text field.
font UIFont? The font of the entered text.
secureTextEntry Bool When set to true, any input in the text field will be secure (i.e. masked with "•" characters).
keyboardAppearance UIKeyboardAppearance The keyboard appearance when editing text in the text field.
borderStyle UITextBorderStyle The border style for the text field.

Additionally, CardTextField offers attributes tailored to its purpose (accessible from interface builder as well):

Property Type Description
cardNumberSeparator String? A string that is used to separate the groups in a card number. Defaults to " - ".
viewAnimationDuration Double? The duration for a view animation in seconds when switching between the card number text field and details (month, view and cvc text fields).
invalidInputColor UIColor? The text color for invalid input. When entering an invalid card number, the text will flash in this color and in case of an expired card, the expiry will be displayed in this color as well.

CardIO

CardIO might be among the most powerful tools to let users enter their payment card information. It uses the camera and lets the user scan his or her credit card. However, you might still want to provide users with a visually appealing text field to enter their payment card information, since users might want to restrict access to their camera or simply want to enter this information manually.

In order to provide users with a link to CardIO, you can use a CardTextField's prefillCardInformation method alongside the previously mentioned accessory button:

// 1. Let your view controller confirm to the CardTextFieldDelegate and CardIOPaymentViewControllerDelegate protocol:
class ViewController: UIViewController, CardTextFieldDelegate, CardIOPaymentViewControllerDelegate {
	...
	
	// MARK: - CardTextFieldDelegate
    func cardTextField(_ cardTextField: CardTextField, didEnterCardInformation information: Card, withValidationResult validationResult: CardValidationResult) {
        if validationResult == .Valid {
        	// A valid payment card has been manually entered or CardIO was used to scan one.
        }
    }
    
    // 2. Optionally provide an image for the CardIO button
    func cardTextFieldShouldShowAccessoryImage(_ cardTextField: CardTextField) -> UIImage? {
        return UIImage(named: "cardIOIcon")
    }
    
    // 3. Set the action for the accessoryButton to open CardIO:
    func cardTextFieldShouldProvideAccessoryAction(_ cardTextField: CardTextField) -> (() -> ())? {
        return { [weak self] _ in
            let cardIOViewController = CardIOPaymentViewController(paymentDelegate: self)
            self?.presentViewController(cardIOViewController, animated: true, completion: nil)
        }
    }
    
    // MARK: - CardIOPaymentViewControllerDelegate
    
    // 4. When receiving payment card information from CardIO, prefill the text field with that information:
    func userDidProvideCreditCardInfo(cardInfo: CardIOCreditCardInfo!, inPaymentViewController paymentViewController: CardIOPaymentViewController!) {
        cardTextField.prefillCardInformation(
        	cardInfo.cardNumber, 
        	month: Int(cardInfo.expiryMonth), 
        	year: Int(cardInfo.expiryYear), 
        	cvc: cardInfo.cvv)
        	
        paymentViewController.dismissViewControllerAnimated(true, completion: nil)
    }
    
    func userDidCancelPaymentViewController(paymentViewController: CardIOPaymentViewController!) {
        paymentViewController.dismissViewControllerAnimated(true, completion: nil)
    }
}

Specifying your own card types

CardTextField further contains a CardTypeRegister which maintains a set of different card types that are accepted by this text field. You can create your own card types and add or remove them to or from card number text fields:

struct MyCardType: CardType {
    
    // MARK: - Required

    // The name of your specified card type:
    public let name = "My Card Type"

    // Note: The image that will be displayed in the card number text field's image view when this card type has been detected will load from an asset with the name `cardType.name`.

    // If the Issuer Identification Number (the first six digits of the entered card number) of a card number 
    // starts with anything from 1000 to 1111, the card is identified as being of type "MyCardType":
    public let identifyingDigits = Set(1000...1111)

    // The number of digits expected in the Card Validation Code.
    public let CVCLength = 4

    // MARK: - Optional

    // The grouping of your card number type. The following results in a card number format
    // like "100 - 0000 - 00000 - 000000":
    // Not specifying this will result in a 16 digit number, separated into 4 groups of 4 digits.
    public let numberGrouping = [3, 4, 5, 6]

    /** 
     A boolean flag that indicates whether CVC validation is required for this card type or not.
     Setting this value to false will hide the CVC text field from the `CardTextField` and remove the required validation routine. The default is true.
     */
    public let requiresCVC = true

    /**
     A boolean flag that indicates whether expiry validation is required for this card type or not.
     Setting this value to false will hide the month and year text field from the `CardTextField` and remove the required
     validation routine. The default is true.
     */
    public let requiresExpiry = true
	
    public init() {
		
    }
}

...

class MyViewController: UIViewController, CardTextFieldDelegate {
	
	@IBOutlet weak var cardTextField: CardTextField?
	...
	
	func viewDidLoad() {
		cardTextField?.cardTypeRegister.registerCardType(MyCardType())
	}
	
	...
}

Using the different components of the text field separately

Instead of entering the card number, expiry and CVC in a single text field, it is possible to use single text fields to enter this information separately.

class ViewController: UIViewController, NumberInputTextFieldDelegate, CardInfoTextFieldDelegate {
    
    @IBOutlet weak var cardNumberTextField: NumberInputTextField!
    @IBOutlet weak var monthInputTextField: MonthInputTextField!
    @IBOutlet weak var yearInputTextField: YearInputTextField!
    @IBOutlet weak var cvcInputTextField: CVCInputTextField!
        
    // A card that is not nil when valid information has been entered in the text fields:
    var card: Card? {
        let number = cardNumberTextField.cardNumber
        let cvc = CVC(rawValue: cvcInputTextField.text ?? "")
        let expiry = Expiry(month: monthInputTextField.text ?? "", year: yearInputTextField.text ?? "")
                        ?? Expiry.invalid
        
        let cardType = cardNumberTextField.cardTypeRegister.cardTypeFor(number: cardNumberTextField.cardNumber)
        if cardType.validate(cvc: cvc).union(cardType.validate(expiry: expiry)).union(cardType.validate(number: number)) == .Valid {
            return Card(number: number, cvc: cvc, expiry: expiry)
        } else {
            return nil
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        cardNumberTextField.numberInputTextFieldDelegate = self
        monthInputTextField.cardInfoTextFieldDelegate = self
        yearInputTextField.cardInfoTextFieldDelegate = self
        cvcInputTextField.cardInfoTextFieldDelegate = self
        
        // Set the `deleteBackwardCallbacks` - closures which are called whenever a user hits
        // backspace on an empty text field.
        monthInputTextField.deleteBackwardCallback = { _ in self.cardNumberTextField.becomeFirstResponder() }
        yearInputTextField.deleteBackwardCallback = { _ in self.monthInputTextField.becomeFirstResponder() }
        cvcInputTextField.deleteBackwardCallback = { _ in self.yearInputTextField.becomeFirstResponder() }
    }
    
    func numberInputTextFieldDidComplete(_ numberInputTextField: NumberInputTextField) {
        cvcInputTextField.cardType = numberInputTextField.cardTypeRegister.cardTypeFor(number: numberInputTextField.cardNumber)
        print("Card number: \(numberInputTextField.cardNumber)")
        print(card)
        monthInputTextField.becomeFirstResponder()
    }
    
    func numberInputTextFieldDidChangeText(_ numberInputTextField: NumberInputTextField) {
        
    }
    
    func textField(_ textField: UITextField, didEnterValidInfo: String) {
        switch textField {
        case is MonthInputTextField:
            print("Month: \(didEnterValidInfo)")
            yearInputTextField.becomeFirstResponder()
        case is YearInputTextField:
            print("Year: \(didEnterValidInfo)")
            cvcInputTextField.becomeFirstResponder()
        case is CVCInputTextField:
            print("CVC: \(didEnterValidInfo)")
        default:
            break
        }
        print(card)
    }
    
    func textField(_ textField: UITextField, didEnterPartiallyValidInfo: String) {
        // The user entered information that is not valid but might become valid on further input.
        // Example: Entering "1" for the CVC is partially valid, while entering "a" is not.
    }
    
    func textField(_ textField: UITextField, didEnterOverflowInfo overFlowDigits: String) {
        // This function is used in a CardTextField to carry digits to the next text field.
        // Example: A user entered "02/20" as expiry and now tries to append "5" to the month.
        //          On a card text field, the year will be replaced with "5" - the overflow digit.
    }
    
    // ...
}

Contributing to Caishen

To report a bug or enhancement request, feel free to file an issue under the respective heading.

If you wish to contribute to the project, fork this repo and submit a pull request. Code contributions should follow the standards specified in the Prolific Swift Style Guide.

License

Caishen is Copyright (c) 2017 Prolific Interactive. It may be redistributed under the terms specified in the LICENSE file.

Maintainers

prolific

Caishen is maintained and funded by Prolific Interactive. The names and logos are trademarks of Prolific Interactive.

More Repositories

1

material-calendarview

A Material design back port of Android's CalendarView
Java
5,903
star
2

ParallaxPager

Add some depth to your Android scrolling.
Java
779
star
3

Yoshi

A convenient wrapper around the UI code that is often needed for displaying debug menus.
Swift
267
star
4

Chandelier

A nice swipe layout that provides new actions with a material design look and feel
Java
240
star
5

swift-style-guide

A style guide for Swift.
172
star
6

SamMitiAR-iOS

Ready-and-easy-to-use ARKit framework for the best user experience.
Swift
120
star
7

node-html-to-json

Parses HTML strings into objects using flexible, composable filters.
JavaScript
119
star
8

PIDatePicker

[DEPRECATED] A customizable implementation of UIDatePicker, written in Swift.
Swift
39
star
9

navigation-conductor

A Conductor integration for the Navigation Architecture Component.
Kotlin
38
star
10

NavigationControllerBlurTransition

[DEPRECATED] A UINavigationController transition that utilizes a blur view for a simple interface.
Swift
35
star
11

android-studio-templates

A set of templates for your Android Studio
FreeMarker
35
star
12

HouseOfCards

Android tools for working with a house of (credit) cards
Java
27
star
13

Optik

A Swift library for displaying images from any source, local or remote.
Swift
25
star
14

PIAPIEnvironmentManager

[DEPRECATED] A simple manager for handling the various API Environments in your project.
Objective-C
20
star
15

simcoe

A simple, light analytics framework for iOS.
Swift
19
star
16

SOLID-Principles

Exploring the SOLID Principles in Swift. Video: https://www.youtube.com/watch?v=gkxmeWvGEpU&t=2s
Swift
19
star
17

anchored-behavior

A CoordinatorLayout Behavior to anchor views with an animation.
Kotlin
17
star
18

Kumi-iOS

Swift
15
star
19

Marker

A light wrapper around NSAttributedString.
Swift
15
star
20

TouchIDBlogPost

Accompanies the tutorial "Use Touch ID in Your Swift App"
Swift
12
star
21

Bellerophon

Swift
12
star
22

Pilas

A scrollable stackview.
Swift
9
star
23

applepay-demo

Objective-C
9
star
24

mabi

Start your REST Mobile APIs Fast and Build as you Grow
C
8
star
25

heimdall

A simple validation check overview for you password fields.
Java
8
star
26

PIVideoPlayer

[DEPRECATED] A custom wrapper around AVFoundation for playing silent video files without any chrome.
Objective-C
7
star
27

Velar

A custom alert view presenter.
Swift
6
star
28

geocoder

This is a device independent and plugable replacement for Android's builtin Geocoder.
Kotlin
6
star
29

ShimmerBlocks

Add blocked shimmering views to your view components.
Swift
5
star
30

Cake

[DEPRECATED] A Cocoapods wrapper allowing for greater control over your workspaces
Swift
5
star
31

ballad

Assemble API Blueprint specs with concatenation, templating, and inheritance.
HTML
4
star
32

patrons

SharedPreferences wrappers with an encryption package.
Kotlin
3
star
33

DeathStar

Sample project for the "Conquering the Testing Beast" talk for Swift Camp (http://swiftcamp.io/)
Swift
3
star
34

behalf

Emulate the way browsers make requests and manage cookies.
JavaScript
3
star
35

TickerCounter

A counter with a ticker animation.
Swift
2
star
36

simplesamlphp-module-mongodb

SimpleSAML Store implementation for MongoDB PHP Library
PHP
2
star
37

data-builder

A build tool for JSON and YAML that uses special keys to specify functions, i.e. $import.
JavaScript
2
star
38

flutter_debug_menu

Flutter Debug Menu
Dart
2
star
39

Birdo

Prolific's android wrapper around the UI code that is often needed for displaying debug menus.
Kotlin
1
star
40

glenlivet

Create flexible, reusable processing pipelines powered by plugins.
JavaScript
1
star
41

mabiSkeletonApi

PHP
1
star
42

Olapic-SDK-iOS

Objective-C
1
star
43

prolific-cleaner

Sets up javascript projects to be linted and checked for code styles based on commit and push git hooks.
JavaScript
1
star
44

IQKeyboardManager

Objective-C
1
star
45

simcoe-android

A simple, light analytics framework for Android.
Kotlin
1
star
46

DevKit

Collection of commonly used swift code
Swift
1
star
47

simplesamlphp-module-mongo

SimpleSAML Store implementation for MongoDB
PHP
1
star
48

PIPassiveAlert

[DEPRECATED] A passive alert library in Objective-C. 🚨
Objective-C
1
star
49

pandroid-gradle-plugin

The PAndroid Gradle plugin allows all Prolific's Android project to run on our CI pipeline for different build variants.
Groovy
1
star
50

artgun-php

PHP ArtGun API Wrapper
PHP
1
star