Skip to content

Commit

Permalink
feat: Stripe ACH Drop-in implementation (#921)
Browse files Browse the repository at this point in the history
* chore: StripeAchUserDetailsComponent structure

Defining StripeAchUserDetailsComponent structure and its models and protocols and errors

* chore: update component

* chore: updated start and submit methods with the client session delegates

* chore: update TokenizationManager

* chore: update payment method types with STRIPE_ACH

* chore: updated Tokenization component and manager and change updateCollectedData logic

* chore: Sample app and updates

* chore: update namings

* chore: Renaming of services and separate tokenization logic from clientSession logic

* chore: update naming across the implementation

* chore: renaming step

* chore: name changing

* chore: rename the Stripe folder

* chore: update the podfile with the path for stripe wrapper

* chore: update the naming and logic for generic implementation

* chore: update the file naming

* chore: update implementation to conform new naming of ACH layer

* chore: more renaming regarding the tokenization service and client session service

* chore: Re-organize ACH tree

* chore: more renaming

* chore: rename isEqual method for ACHUserDetails

* chore: update clientToken and PrimerError and add stripeOptions

* chore: update naming for ach layer

* chore: Added unit tests

* chore: cleanup merchant vm

* chore: update payment method tokenization vm

* chore: updated StripeAchTokenizationViewModel according to latest requests

* chore: update after merge

* chore: update unit tests

* chore: added more unit tests

* chore: resolve merge conflict

* chore: added tokenization viewModel unit test

* chore: update after merge

* chore: update unit test

* chore: update unit tests

* chore: update the stripeBankCollector logic

update the stripeBankCollector logic
add stripe ach payment method logic to the merchant sample app

* chore: update after merge

* chore: update unit tests

* chore: update unit test

* chore: update unit tests path

* chore: E2E tests update

* chore: update for Vault Manger support in E2E tests

* chore: update after merging with master

* chore: complete call integration

* chore: update and check session user details on get method

* chore: update AdditionalInfo classes making properties public

* chore: update support for vaulting

* chore: fix double assigning

* chore: fix complete url naming

* chore: update unit tests

* chore: update tokenization viewModel for unit tests refactor and E2E UI tests

* chore: added stripe wrapper to packages and moved unit tests

* chore: fix package

* chore: updated unit tests and moved tokenization viewmodel tests

* chore: fix import wrapper

* chore: added more wrapping

* chore: added stripe wrapper to the package

* chore: remove wrapper import from unit tests

* chore: fix git path for private access and unit test

* chore: renaming of PrimerStripeOptions

* chore: update naming on the component

* chore: renaming ACH additional info

* chore: renaming of additionalInfo class

* chore: refactor showing results screen

* chore: refactor tokenization

* chore: added drop-in logic

Updated tokenization viewModel to support drop-in logic
Added Drop-In ACHUserDetailViewController with swiftUI views

* chore: added ACHUserDetailsView as in designs

* chore: update validator

* chore: added mandate view

* chore: add new Primer UI Results screen design and logic

* chore: add mandateData and update the views and logic

* chore: revert merchant available payment methods changes

* chore: nit

* chore: added error support for tokenizationViewModel into ACH Component

* chore: nit on error logic

* chore: update merchant example for handling errors

* chore: sync with headless update

* chore: updated PrimerCustomResult screen

* chore: update primerError handling

* chore: revert tokenizationVM error handling

* chore: refactor mandate screen

* chore: navigation back to payment method origin screen

* chore: swift lint warnings

* chore: update Complete response body and method descriptions

* chore: update Complete response body

* chore: added ACHUserDetailsViewController unit tests

* chore: added mandate vc and vm and primer custom result vc and vm unit tests

* chore: updated failing unit tests

* chore: alignment with android and complete method refactor

Finishing the unit tests with complete method
Fixed patching issue

* chore: commentary nits

* chore: change stripe sdk wrapper source in podfile

* chore: updated Drop-in UI and added stripe ach logo

* chore: Mandate screen dismiss actions

* chore: update with localization strings

* chore: update accessibility and localization keys

* chore: switch accessibility tags in result view

* chore: update podfile

* chore: update SPM for stripe wrapper cocoa release version

* chore: added unit test for ACHAdditionalInfo

* chore: update with client-session/actions call

* chore: clean UserDetails screen

* chore: fixed clientSession service tests

* chore: update failing unit test

* chore: added vaultManager unit test

* chore: fixed unit test

* chore: fix linting issues

* chore: fix unit test

* chore: fixed CustomResult unit tests

* chore: fix stripe component unit test

* chore: fix unit test and update Packages

* chore: fixed path for primer-stripe-sdk

* chore: update complete call

* chore: added more unit tests

* chore: PR review requested changes

* chore: fixed failing unit tests

* chore: removing IDEWorkspaceChecks

* Bump sdk test xcode version to 15.4

* Switch to macOS 14

* Switch to macOS 14 for debug app tests too

* Update sim version in fastfile

* create separate var for default sim version

* Bump sim version to 17.5

* Bump debug app job to macos 14

* Bump debug app job to macos 14 #2

* Use xcode 15.4 for SPM job

* add missing vc to spm target

* Disable nesting rule for strings file

* lint

* lint tweaks

* Update pods

* fix mandate validation unit test

---------

Co-authored-by: Jack Newcombe <[email protected]>
  • Loading branch information
StefanV-PRIMERIO and jnewc authored Aug 29, 2024
1 parent 6150143 commit 98c812f
Show file tree
Hide file tree
Showing 36 changed files with 2,097 additions and 136 deletions.
14 changes: 9 additions & 5 deletions Debug App/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ PODS:
- PrimerIPay88MYSDK (0.1.7)
- PrimerKlarnaSDK (1.1.1)
- PrimerNolPaySDK (1.0.1)
- PrimerSDK (2.29.0):
- PrimerSDK/Core (= 2.29.0)
- PrimerSDK/Core (2.29.0)
- PrimerSDK (2.30.1):
- PrimerSDK/Core (= 2.30.1)
- PrimerSDK/Core (2.30.1)
- PrimerStripeSDK (1.0.0)

DEPENDENCIES:
- IQKeyboardManagerSwift
Expand All @@ -15,6 +16,7 @@ DEPENDENCIES:
- PrimerKlarnaSDK
- PrimerNolPaySDK
- PrimerSDK (from `../`)
- PrimerStripeSDK

SPEC REPOS:
trunk:
Expand All @@ -23,6 +25,7 @@ SPEC REPOS:
- PrimerIPay88MYSDK
- PrimerKlarnaSDK
- PrimerNolPaySDK
- PrimerStripeSDK

EXTERNAL SOURCES:
PrimerSDK:
Expand All @@ -34,8 +37,9 @@ SPEC CHECKSUMS:
PrimerIPay88MYSDK: 436ee0be7e2c97e4e81456ccddee20175e9e3c4d
PrimerKlarnaSDK: 564105170cc7b467bf95c31851813ea41c468f8b
PrimerNolPaySDK: 08b140ed39b378a0b33b4f8746544a402175c0cc
PrimerSDK: 2cdf97c9a306fe5b3389f0b9c5bf05e7693cbb60
PrimerSDK: e14fcc7357d743e1aeec6afdcae27ce7e21f02a5
PrimerStripeSDK: c37d4e7c1b5256d67d4890c4cc4b38ddc9427489

PODFILE CHECKSUM: fb702ce88fd7db36a91e9ed9e4fe87609ec9aff9
PODFILE CHECKSUM: fa17ead44d40b0b09abc2f30a5cc3d8aefe389e1

COCOAPODS: 1.15.2
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,8 @@ class MerchantSessionAndSettingsViewController: UIViewController {
isErrorScreenEnabled: !disableErrorScreenSwitch.isOn,
theme: applyThemingSwitch.isOn ? CheckoutTheme.tropical : nil)

let mandateData = PrimerStripeOptions.MandateData.templateMandate(merchantName: "Primer Inc.")

let settings = PrimerSettings(
paymentHandling: selectedPaymentHandling,
paymentMethodOptions: PrimerPaymentMethodOptions(
Expand All @@ -549,8 +551,7 @@ class MerchantSessionAndSettingsViewController: UIViewController {
isCaptureBillingAddressEnabled: false,
showApplePayForUnsupportedDevice: false,
checkProvidedNetworks: false),
stripeOptions: PrimerStripeOptions(
publishableKey: MerchantMockDataManager.stripePublishableKey)),
stripeOptions: PrimerStripeOptions(publishableKey: MerchantMockDataManager.stripePublishableKey, mandateData: mandateData)),
uiOptions: uiOptions,
debugOptions: PrimerDebugOptions(is3DSSanityCheckEnabled: false)
)
Expand Down
2 changes: 1 addition & 1 deletion Package.Development.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
.package(url: "https://github.com/primer-io/primer-sdk-3ds-ios", from: "2.3.1"),
.package(url: "https://github.com/primer-io/primer-klarna-sdk-ios", from: "1.1.1"),
.package(url: "https://github.com/primer-io/primer-nol-pay-sdk-ios", from: "1.0.2"),
.package(url: "git@github.com:primer-io/primer-stripe-sdk-ios", from: "1.0.0")
.package(url: "https://github.com/primer-io/primer-stripe-sdk-ios", from: "1.0.0")
],
targets: [
.target(
Expand Down
2 changes: 1 addition & 1 deletion Package.Stripe.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let package = Package(
)
],
dependencies: [
.package(url: "git@github.com:primer-io/primer-stripe-sdk-ios", .branch("nq/initial_setup"))
.package(url: "https://github.com/primer-io/primer-stripe-sdk-ios", from: "1.0.0")
],
targets: [
.target(
Expand Down
205 changes: 205 additions & 0 deletions Sources/PrimerSDK/Classes/Core/Constants/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// swiftlint:disable file_length
// swiftlint:disable type_body_length
// swiftlint:disable line_length
// swiftlint:disable nesting

import Foundation

Expand Down Expand Up @@ -1076,6 +1077,210 @@ extension Strings {
comment: "The title of the mocked failed flow for a Test Payment Method")
}
}

// MARK: - ACH

extension Strings {

struct UserDetails {

static let subtitle = NSLocalizedString(
"stripe_ach_user_details_collection_subtitle_label",
tableName: nil,
bundle: Bundle.primerResources,
value: "Your personal details",
comment: "The subtitle label of User Details screen"
)

static let continueButton = NSLocalizedString(
"stripe_ach_user_details_collection_continue_button",
tableName: nil,
bundle: Bundle.primerResources,
value: "Continue",
comment: "The continue button title of User Details screen"
)

static let backButton = NSLocalizedString(
"back_button_label",
tableName: nil,
bundle: Bundle.primerResources,
value: "Back",
comment: "The back button title of User Details screen"
)

static let emailDisclaimer = NSLocalizedString(
"stripe_ach_user_details_collection_data_usage_label",
tableName: nil,
bundle: Bundle.primerResources,
value: "We'll only use this to keep you updated about your payment",
comment: "The email disclaimer label of User Details screen"
)

// swiftlint:disable:next nesting
enum FirstName {

static let label = NSLocalizedString(
"stripe_ach_user_details_collection_first_name_label",
tableName: nil,
bundle: Bundle.primerResources,
value: "First name",
comment: "The first name textfield label"
)

static let errorDescriptorField = NSLocalizedString(
"stripe_ach_user_details_collection_invalid_first_name",
tableName: nil,
bundle: Bundle.primerResources,
value: "Please enter a valid first name. Avoid using numbers or special characters.",
comment: "First name error descriptor - Form Validation"
)
}

// swiftlint:disable:next nesting
enum LastName {

static let label = NSLocalizedString(
"stripe_ach_user_details_collection_last_name_label",
tableName: nil,
bundle: Bundle.primerResources,
value: "Last name",
comment: "The last name textfield label"
)

static let errorDescriptorField = NSLocalizedString(
"stripe_ach_user_details_collection_invalid_last_name",
tableName: nil,
bundle: Bundle.primerResources,
value: "Please enter a valid last name. Avoid using numbers or special characters.",
comment: "Last name error descriptor - Form Validation"
)
}

// swiftlint:disable:next nesting
enum EmailAddress {

static let label = NSLocalizedString(
"stripe_ach_user_details_collection_email_address_label",
tableName: nil,
bundle: Bundle.primerResources,
value: "Email address",
comment: "The email address textfield label"
)

static let errorDescriptorField = NSLocalizedString(
"stripe_ach_user_details_collection_invalid_email_address",
tableName: nil,
bundle: Bundle.primerResources,
value: "The email address you entered doesn't look like a real email address. Please make sure it includes an '@' and a domain (like '@example.com').",
comment: "Email address error descriptor - Form Validation"
)
}

}

struct Mandate {

static let templateText = NSLocalizedString(
"stripe_ach_mandate_template_ios",
tableName: nil,
bundle: Bundle.primerResources,
value: "By clicking Accept, you authorize %@ to debit the bank account specified above for any amount owed for charges arising from your use of %@'s services and/or purchase of products from %@, pursuant to %@'s website and terms, until this authorization is revoked. You may amend or cancel this authorization at any time by providing notice to %@ with 30 (thirty) days notice.\n\nIf you use %@'s services or purchase additional products periodically pursuant to %@'s terms, you authorize %@ to debit your bank account periodically. Payments that fall outside the regular debits authorized above will only be debited after your authorization is obtained.",
comment: "The template text for mandate info"
)

static let acceptButton = NSLocalizedString(
"stripe_ach_mandate_accept_button",
tableName: nil,
bundle: Bundle.primerResources,
value: "Accept",
comment: "The accept button title for Mandate info"
)

static let cancelButton = NSLocalizedString(
"stripe_ach_mandate_cancel_payment_button",
tableName: nil,
bundle: Bundle.primerResources,
value: "Cancel payment",
comment: "The cancel button title for Mandate info"
)

}

struct ResultView {

static let paymentTitle = NSLocalizedString(
"pay_with_payment_method",
tableName: nil,
bundle: Bundle.primerResources,
value: "Pay with %@",
comment: "The payment method title"
)

static let successMessage = NSLocalizedString(
"stripe_ach_payment_request_completed_successfully",
tableName: nil,
bundle: Bundle.primerResources,
value: "You have now authorized your bank account to be debited. You will be notified via email once the payment has been collected successfully.",
comment: "The success message for ResultView"
)

static let cancelMessage = NSLocalizedString(
"stripe_ach_payment_request_cancelled",
tableName: nil,
bundle: Bundle.primerResources,
value: "Please try again or select another bank",
comment: "The cancel message for ResultView"
)

static let retryButton = NSLocalizedString(
"retry_button",
tableName: nil,
bundle: Bundle.primerResources,
value: "Retry",
comment: "The retry button title for ResultView"
)

static let chooseOtherPM = NSLocalizedString(
"choose_other_payment_method_button",
tableName: nil,
bundle: Bundle.primerResources,
value: "Choose other payment method",
comment: "The choose other PM button title for ResultView"
)

enum Subtitle {

static let successful = NSLocalizedString(
"session_complete_payment_success_title",
tableName: nil,
bundle: Bundle.primerResources,
value: "Payment authorized",
comment: "The subtitle for ResultView - Success state"
)

static let cancelled = NSLocalizedString(
"session_complete_payment_cancellation_title",
tableName: nil,
bundle: Bundle.primerResources,
value: "Payment cancelled",
comment: "The subtitle for ResultView - Cancelled state"
)

static let failed = NSLocalizedString(
"session_complete_payment_failure_title",
tableName: nil,
bundle: Bundle.primerResources,
value: "Payment failed",
comment: "The subtitle for ResultView - Failed state"
)

}

}

}

// swiftlint:enable type_body_length
// swiftlint:enable line_length
// swiftlint:enable nesting
// swiftlint:enable file_length
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Foundation
* - `acceptMandate()`: Called when the user accepts the mandate, enabling completion of transaction.
* - `declineMandate()`: Called when the user declines the mandate.
*/
public protocol ACHMandateDelegate {
public protocol ACHMandateDelegate: AnyObject {
func acceptMandate()
func declineMandate()
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ public class ACHUserDetails: Codable {
public var firstName: String
public var lastName: String
public var emailAddress: String



public init(firstName: String, lastName: String, emailAddress: String) {
self.firstName = firstName
self.lastName = lastName
Expand All @@ -59,7 +58,7 @@ extension ACHUserDetails: ACHUserDetailsHandling {
emailAddress = value
}
}

public static func emptyUserDetails() -> ACHUserDetails {
return ACHUserDetails(firstName: "", lastName: "", emailAddress: "")
}
Expand Down Expand Up @@ -95,7 +94,7 @@ extension ACHUserDetails: Equatable {
unequalFields.append(ACHUserDetailsError.invalidEmailAddress)
areEqual = false
}

return (areEqual, unequalFields)
}
}
Loading

0 comments on commit 98c812f

Please sign in to comment.