Skip to content

Commit

Permalink
Merge branch 'jn/co-badged-cards-example' into u/at/update-readme
Browse files Browse the repository at this point in the history
  • Loading branch information
jnewc committed Feb 2, 2024
2 parents bd2b58e + d63e9bd commit b74f8c9
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 86 deletions.
73 changes: 73 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Contributing to our repos

At Primer, we believe that endorsing and maintaining open source software is an important step towards building strong bonds with the wider engineering community. We appreciate and actively invite contribution from outside of our organisation.

## Our Standards

We have expectations of our own behaviour and the behaviour of others when interacting with us in our open source repos.

* Use welcoming and inclusive language at all times
* Be empathetic towards others at all times
* Be respectful of differing viewpoints and experiences
* Gracefully accept constructive criticism
* Focus on what is best for the existing members of the community

There are some behaviours and forms of language that we won't tolerate when communicating with us about our software. Again, this applies to both our organisation members and those community members external to it. These include:

* Sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting and/or derogatory comments, and personal or political attacks
* Public or private harassment of individuals or groups
* Publishing others' private information (a.k.a "doxxing"), such as a physical or electronic
address, without explicit permission from the owner
* Any other conduct that is antithetical to reasonable and polite discourse

## Our Commitments

We appreciate contributions of all forms, but we are not obliged to accept them such that we will make changes to our software or repos to accommodate them.

We will always make best efforts to discuss contributions, but are not obliged to provide timelines, contact details, or other information concerning commitments that we may or may not make to the contribution in question.


## Reporting issues

A bug 🐛 is something nobody ever wants to receive. But, they happen 🤷‍♂️.

Please read the following guidelines before you [report an issue](../issues):

1. **Use the GitHub issue search** — check if the issue has already been
reported. If it has been, please comment on the existing issue. If it is closed,
please reference the closed issue in your new issue report.

2. **Check if the issue has been fixed** — the latest `master`/`main` or
development branch may already contain a fix.

3. **Isolate the problem** — make sure that the code in the
project's repository is responsible for the issue. Create a broken-down list of steps or an
extremely simple and immediately viewable example of the issue.

4. **Include a live example** — provide a link, screenshot, video
or anything that could help us understand better the domain.

Other advice for reporting an issue:

* Please try to be as detailed as possible in your report.
* Please include: what steps will reproduce the issue, which browsers, devices and/or OS
you have recreated the issue with.
* Please state what would you expect the outcome to be.

## Pull requests

Good pull requests — patches, improvements, new features — are a fantastic
help. They should remain focused in scope and avoid containing unrelated
commits.

> [!WARNING]
> If your contribution involves a significant amount of work or substantial
> changes to any part of the project, **please open an issue to discuss it first**.
## Thank you 🙏

👉 from all of the team at Primer ❤️

You are helping us making our product better 👌, faster 🚀, stronger 💪
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ jobs:
with:
access_token: ${{ github.token }}
- name: Git - Checkout
uses: actions/checkout@v3
uses: actions/checkout@v3.6.0
with:
ref: ${{ github.ref }}
- name: Select Xcode Version
uses: maxim-lobanov/setup-xcode@v1
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: '15.0'
- uses: ruby/setup-ruby@v1
- uses: ruby/setup-ruby@v1.171.0
with:
ruby-version: "3.2"
bundler-cache: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
044F26AE2B5995EE00CEFEBC /* PrimerSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 044F26AD2B5995EE00CEFEBC /* PrimerSDK */; };
0471BB072AFD1FFB000B286D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0471BB062AFD1FFB000B286D /* ContentView.swift */; };
0479FB1B2AF270B900D0AFC9 /* PrimerDataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0479FB1A2AF270B900D0AFC9 /* PrimerDataService.swift */; };
04819DAD2B63A8D8004BBDE1 /* PrimerCardDataErrorsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04819DAC2B63A8D8004BBDE1 /* PrimerCardDataErrorsModel.swift */; };
04AFFEE72B02797A002B2019 /* StartPage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AFFEE62B02797A002B2019 /* StartPage.swift */; };
04D781562AF517BD00A3B29B /* ImageColorInverterModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04D781552AF517BD00A3B29B /* ImageColorInverterModifier.swift */; };
04D79C5C2B59446500E6448E /* PrimerSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 04D79C5B2B59446500E6448E /* PrimerSDK */; };
Expand Down Expand Up @@ -83,6 +84,7 @@
04420E782B14A84500EA8790 /* ExampleAppLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleAppLogger.swift; sourceTree = "<group>"; };
0471BB062AFD1FFB000B286D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
0479FB1A2AF270B900D0AFC9 /* PrimerDataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimerDataService.swift; sourceTree = "<group>"; };
04819DAC2B63A8D8004BBDE1 /* PrimerCardDataErrorsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimerCardDataErrorsModel.swift; sourceTree = "<group>"; };
04AFFEE62B02797A002B2019 /* StartPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartPage.swift; sourceTree = "<group>"; };
04D781552AF517BD00A3B29B /* ImageColorInverterModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageColorInverterModifier.swift; sourceTree = "<group>"; };
04DAAECE2B03CA9F002E2614 /* SettingsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -278,8 +280,9 @@
isa = PBXGroup;
children = (
04FAFA2D2AE90049002E4BAE /* PrimerCardDataModel.swift */,
04DAAECE2B03CA9F002E2614 /* SettingsModel.swift */,
04819DAC2B63A8D8004BBDE1 /* PrimerCardDataErrorsModel.swift */,
04345A392B1F865C006B40CC /* PaymentResultModel.swift */,
04DAAECE2B03CA9F002E2614 /* SettingsModel.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -453,6 +456,7 @@
04345A4A2B21D6F1006B40CC /* Appearance.swift in Sources */,
04FAFA2E2AE90049002E4BAE /* PrimerCardDataModel.swift in Sources */,
04345A3E2B1F8D0E006B40CC /* Font+Monospace.swift in Sources */,
04819DAD2B63A8D8004BBDE1 /* PrimerCardDataErrorsModel.swift in Sources */,
040050C22AFE5CEB001843CA /* SettingsView.swift in Sources */,
04FAFA2A2AE81850002E4BAE /* PrimerTextField.swift in Sources */,
04345A402B208046006B40CC /* FormInfo.swift in Sources */,
Expand Down Expand Up @@ -810,7 +814,7 @@
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/primer-io/primer-sdk-ios.git";
requirement = {
branch = "jn/co-badged-cards/dx-align";
branch = "feature/co-badged-cards";
kind = branch;
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
{
"pins" : [
{
"identity" : "primer-klarna-sdk-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/primer-io/primer-klarna-sdk-ios.git",
"state" : {
"revision" : "f13260c24a900f28e21bafd213c22191e6280e86",
"version" : "1.0.4"
}
},
{
"identity" : "primer-sdk-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/primer-io/primer-sdk-ios.git",
"state" : {
"branch" : "jn/co-badged-cards/dx-align",
"revision" : "b3c81108ea74ac4761a21770bc1ad47deb668142"
"branch" : "feature/co-badged-cards",
"revision" : "c64d97de8c02b36d19ed9baafbe66bfc610b44fd"
}
}
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// PrimerCardDataErrorsModel.swift
// Co-Badged Cards Example
//
// Created by Jack Newcombe on 26/01/2024.
//

import Foundation
import PrimerSDK

class PrimerCardDataErrorsModel: PrimerBaseCardDataModel {

override init() {
super.init()
logger.info("[PrimerCardDataErrorsModel.init]")
}

fileprivate func clearErrors() {
self.cardNumber = ""
self.expiryDate = ""
self.cvvNumber = ""
self.cardholderName = ""
self.selectedCardNetwork = .unknown
}

var hasErrors: Bool {
!(
cardNumber.isEmpty &&
expiryDate.isEmpty &&
cvvNumber.isEmpty &&
cardholderName.isEmpty
)
}
}

extension PrimerCardDataErrorsModel: PrimerDataServiceErrorsDelegate {
func didReceiveErrors(errors: [Error]) {
logger.info("[PrimerCardDataModel.didReceiveErrors]")
DispatchQueue.main.async {
self.clearErrors()

let validationErrors = errors.reversed().compactMap { $0 as? PrimerValidationError }

validationErrors.forEach { error in
logger.info("[PrimerCardDataModel.didReceiveErrors] error => \(error.errorId)")
switch error {
case .invalidCardnumber(let message, _, _):
self.cardNumber = message
case .invalidCardType(let message, _, _):
self.cardNumber = message // Overrides above
case .invalidExpiryDate(let message, _, _):
self.expiryDate = message
case .invalidCvv(let message, _, _):
self.cvvNumber = message
case .invalidCardholderName(let message, _, _):
self.cardholderName = message
default: break
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Combine
import PrimerSDK

class PrimerBaseCardDataModel: ObservableObject {

@Published var cardNumber: String = ""

@Published var cvvNumber: String = ""
Expand All @@ -28,7 +29,11 @@ class PrimerBaseCardDataModel: ObservableObject {
}

class PrimerLoadingModel: ObservableObject {
var isLoading: Bool = false
@Published var isLoading: Bool = false
}

class PrimerCardNetworksModel: ObservableObject {
var cardNetworks: [CardDisplayModel] = []
}

class PrimerCardDataModel: PrimerBaseCardDataModel {
Expand All @@ -37,81 +42,60 @@ class PrimerCardDataModel: PrimerBaseCardDataModel {

var loadingModel: PrimerLoadingModel = PrimerLoadingModel()

@Published var cardNetworkModels: [CardDisplayModel] = [] {
didSet {
selectedCardNetwork = .unknown
objectWillChange.send()
}
}
var cardNetworksModel: PrimerCardNetworksModel = PrimerCardNetworksModel()

var shouldDisplayCardSelectionView: Bool {
return !cardNumber.isEmpty && !cardNetworkModels.isEmpty
return !cardNumber.isEmpty && !cardNetworksModel.cardNetworks.isEmpty
}

weak var service: PrimerDataService?

func updateCardNetworks(with networks: [CardDisplayModel]) {
cardNetworksModel.cardNetworks = networks
if !networks.isEmpty {
selectCardNetwork(at: 0)
}
}

func selectCardNetwork(at index: Int) {
selectedCardNetwork = cardNetworkModels[index].value
selectedCardNetwork = cardNetworksModel.cardNetworks[index].value
objectWillChange.send()
}

override init() {
super.init()
logger.info("[PrimerCardDataModel.init]")
objectWillChange.sink {
self.service?.update(withModel: self)
DispatchQueue.main.async {
self.service?.update(withModel: self)
}
}.store(in: &cancellables)
}

var isEmpty: Bool {
cardNumber.isEmpty &&
expiryDate.isEmpty &&
cvvNumber.isEmpty &&
cardholderName.isEmpty
}
}

extension PrimerCardDataModel: PrimerDataServiceModelsDelegate {

func willReceiveCardModels() {
logger.info("[PrimerCardDataModel.willReceiveCardModels]")
DispatchQueue.main.async {
self.loadingModel.isLoading = true
}
}

func didReceiveCardModels(models: [CardDisplayModel]) {
logger.info("[PrimerCardDataModel.didReceiveCardModels] => \(models.map { $0.value.rawValue })")
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.loadingModel.isLoading = false
if self.cardNetworkModels.map({ $0.value.rawValue }) != models.map({ $0.value.rawValue }) {
self.cardNetworkModels = models
}
}
}
}

// MARK: Errors Model

class PrimerCardDataErrorsModel: PrimerBaseCardDataModel {

fileprivate func clearErrors() {
self.cardNumber = ""
self.expiryDate = ""
self.cvvNumber = ""
self.cardholderName = ""
self.selectedCardNetwork = .unknown
}
}

extension PrimerCardDataErrorsModel: PrimerDataServiceErrorsDelegate {
func didReceiveErrors(errors: [Error]) {
self.clearErrors()
errors.reversed().compactMap { $0 as? PrimerValidationError }.forEach { error in
switch error {
case .invalidCardnumber(let message, _, _):
self.cardNumber = message
case .invalidCardType(let message, _, _):
self.cardNumber = message // Overrides above
case .invalidExpiryDate(let message, _, _):
self.expiryDate = message
case .invalidCvv(let message, _, _):
self.cvvNumber = message
case .invalidCardholderName(let message, _, _):
self.cardholderName = message
default: break
if self.cardNetworksModel.cardNetworks.map({ $0.value.rawValue }) != models.map({ $0.value.rawValue }) {
self.cardNetworksModel.cardNetworks = models
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class SettingsModel: ObservableObject {
let components = token.components(separatedBy: ".")

guard components.count == 3 else {
return false // JWT should have three parts
return false
}

return components.allSatisfy { (try? /[\w\d=\+\-\/]+/.wholeMatch(in: $0)) != nil }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class PrimerDataService: NSObject {
self.clientToken = clientToken
}

// MARK: SDK Configuration

func fetchClientToken(from url: String) async throws -> String {
do {
var request = URLRequest(url: URL(string: "\(url)/client-session")!)
Expand Down Expand Up @@ -111,16 +113,7 @@ class PrimerDataService: NSObject {

func update(withModel model: PrimerCardDataModel) {
let network = model.selectedCardNetwork == .unknown ? nil : model.selectedCardNetwork

if let previousData = rawDataManager?.rawData as? PrimerCardData,
previousData.cardNumber == model.cardNumber &&
previousData.cardholderName == model.cardholderName &&
previousData.cvv == model.cvvNumber &&
previousData.expiryDate == model.expiryDate &&
previousData.cardNetwork == network {
return
}


rawDataManager?.rawData = PrimerCardData(
cardNumber: model.cardNumber,
expiryDate: model.expiryDate,
Expand Down Expand Up @@ -243,7 +236,7 @@ extension PrimerDataService: PrimerHeadlessUniversalCheckoutRawDataManagerDelega
}

private func image(from model: PrimerCardNetwork) -> UIImage? {
let asset = try? PrimerHeadlessUniversalCheckout.AssetsManager.getCardNetworkAsset(for: model.network)
let asset = PrimerHeadlessUniversalCheckout.AssetsManager.getCardNetworkAsset(for: model.network)
return asset?.cardImage
}
}
Loading

0 comments on commit b74f8c9

Please sign in to comment.