Skip to content

Commit

Permalink
[PAY-1900] [PAY-1898] Create Payment Source From Payment Sheet (#1733)
Browse files Browse the repository at this point in the history
* successfully updating payment methods with new stripe payment sheet card.

* added tests to everything except the view model

* added tests for new card addition via payment sheet.

* test name correction
  • Loading branch information
msadoon committed Sep 21, 2022
1 parent 514fde9 commit e3cb02a
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 79 deletions.
Expand Up @@ -133,6 +133,12 @@ internal final class PaymentMethodsViewController: UIViewController, MessageBann
_ = self?.editButton
?|> \.title %~ { _ in title }
}

self.viewModel.outputs.setStripePublishableKey
.observeForUI()
.observeValues {
STPAPIClient.shared.publishableKey = $0
}
}

// MARK: - Actions
Expand Down Expand Up @@ -174,15 +180,13 @@ internal final class PaymentMethodsViewController: UIViewController, MessageBann
strongSelf.paymentSheetFlowController?.presentPaymentOptions(from: strongSelf) { [weak self] in
guard let strongSelf = self else { return }

/** TODO: https://kickstarter.atlassian.net/browse/PAY-1900
* strongSelf.confirmPaymentResult(with: data.clientSecret)
*/
strongSelf.confirmPaymentResult(with: data.clientSecret)
}
}
}
}

private func confirmPaymentResult(with _: String) {
private func confirmPaymentResult(with clientSecret: String) {
guard self.paymentSheetFlowController?.paymentOption != nil else {
/** TODO: https://kickstarter.atlassian.net/browse/PAY-1954
* strongSelf.viewModel.inputs.shouldCancelPaymentSheetAppearance(state: true)
Expand All @@ -203,10 +207,8 @@ internal final class PaymentMethodsViewController: UIViewController, MessageBann

switch paymentResult {
case .completed:
/** TODO: https://kickstarter.atlassian.net/browse/PAY-1898
* strongSelf.viewModel.inputs.paymentSheetDidAdd(newCard: existingPaymentOption, setupIntent: clientSecret)
*/
fatalError()
strongSelf.viewModel.inputs
.paymentSheetDidAdd(newCard: existingPaymentOption, setupIntent: clientSecret)
case .canceled:
strongSelf.messageBannerViewController?
.showBanner(with: .error, message: Strings.general_error_something_wrong())
Expand Down
20 changes: 20 additions & 0 deletions Kickstarter.xcodeproj/project.pbxproj
Expand Up @@ -234,9 +234,14 @@
19047FCB2889CDAC00BDD1A8 /* GraphAPI.CreateSetupIntentInput+CreateSetupIntentInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19047FCA2889CDAC00BDD1A8 /* GraphAPI.CreateSetupIntentInput+CreateSetupIntentInput.swift */; };
19047FCF2889D4BC00BDD1A8 /* GraphAPI.CreateSetupIntentInput+CreateSetupIntentInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19047FCE2889D4BC00BDD1A8 /* GraphAPI.CreateSetupIntentInput+CreateSetupIntentInputTests.swift */; };
19047FD12889D6DC00BDD1A8 /* ClientSecretEnvelope+CreateSetupIntentMutation.DataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19047FD02889D6DC00BDD1A8 /* ClientSecretEnvelope+CreateSetupIntentMutation.DataTests.swift */; };
1923770A28DA2AE300F68635 /* Stripe+PaymentMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1923770928DA2AE300F68635 /* Stripe+PaymentMethod.swift */; };
1937A71F28C94FFC00DD732D /* SettingsFormFieldView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7790DF882200D3BD005DBB11 /* SettingsFormFieldView.xib */; };
1937A72328C9570A00DD732D /* ErroredBackingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1937A72228C9570900DD732D /* ErroredBackingView.swift */; };
1937A72628C959DD00DD732D /* FundingGraphViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7ED205B1E83240D00BFFA01 /* FundingGraphViewTests.swift */; };
194154CE28D8ED69004648C8 /* CreatePaymentSourceSetupIntentInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194154CD28D8ED69004648C8 /* CreatePaymentSourceSetupIntentInput.swift */; };
194154D128D8FBAA004648C8 /* CreatePaymentSourceSetupIntentClientSecret+Constructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194154CF28D8F2DE004648C8 /* CreatePaymentSourceSetupIntentClientSecret+Constructor.swift */; };
194154D328D928C9004648C8 /* CreatePaymentSourceSetupIntentInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194154D228D928C9004648C8 /* CreatePaymentSourceSetupIntentInputTests.swift */; };
194154D628D931AA004648C8 /* CreatePaymentSourceSetupIntentClientSecret+ConstructorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194154D428D92A26004648C8 /* CreatePaymentSourceSetupIntentClientSecret+ConstructorTests.swift */; };
19450C2628C8225200C60F97 /* SearchViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7ED20381E8323E900BFFA01 /* SearchViewControllerTests.swift */; };
19450C2F28C823A400C60F97 /* SelectCurrencyViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6B9FA1422089C05003282A5 /* SelectCurrencyViewControllerTests.swift */; };
194520C5288859A600CA9B88 /* PaymentCardTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 194520C4288859A600CA9B88 /* PaymentCardTextField.swift */; };
Expand Down Expand Up @@ -1877,7 +1882,12 @@
19047FCE2889D4BC00BDD1A8 /* GraphAPI.CreateSetupIntentInput+CreateSetupIntentInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GraphAPI.CreateSetupIntentInput+CreateSetupIntentInputTests.swift"; sourceTree = "<group>"; };
19047FD02889D6DC00BDD1A8 /* ClientSecretEnvelope+CreateSetupIntentMutation.DataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ClientSecretEnvelope+CreateSetupIntentMutation.DataTests.swift"; sourceTree = "<group>"; };
192016C728B6731A0046919B /* PledgePaymentMethodsViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PledgePaymentMethodsViewControllerTests.swift; sourceTree = "<group>"; };
1923770928DA2AE300F68635 /* Stripe+PaymentMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stripe+PaymentMethod.swift"; sourceTree = "<group>"; };
1937A72228C9570900DD732D /* ErroredBackingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErroredBackingView.swift; sourceTree = "<group>"; };
194154CD28D8ED69004648C8 /* CreatePaymentSourceSetupIntentInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePaymentSourceSetupIntentInput.swift; sourceTree = "<group>"; };
194154CF28D8F2DE004648C8 /* CreatePaymentSourceSetupIntentClientSecret+Constructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CreatePaymentSourceSetupIntentClientSecret+Constructor.swift"; sourceTree = "<group>"; };
194154D228D928C9004648C8 /* CreatePaymentSourceSetupIntentInputTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePaymentSourceSetupIntentInputTests.swift; sourceTree = "<group>"; };
194154D428D92A26004648C8 /* CreatePaymentSourceSetupIntentClientSecret+ConstructorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CreatePaymentSourceSetupIntentClientSecret+ConstructorTests.swift"; sourceTree = "<group>"; };
194520C4288859A600CA9B88 /* PaymentCardTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentCardTextField.swift; sourceTree = "<group>"; };
19A97CF128C7E2D30031B857 /* CategoryPillCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryPillCell.swift; sourceTree = "<group>"; };
19F0940028B3D75800973138 /* PledgePaymentMethodAddCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PledgePaymentMethodAddCellViewModel.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6227,6 +6237,8 @@
77AA2B39233C09F5008BBCB8 /* CreateBackingInput+Constructor.swift */,
8AFB8C96233E9977006779B5 /* CreatePaymentSourceInput+Constructor.swift */,
8AFB8C98233E9A1F006779B5 /* CreatePaymentSourceInput+ConstructorTests.swift */,
194154CF28D8F2DE004648C8 /* CreatePaymentSourceSetupIntentClientSecret+Constructor.swift */,
194154D428D92A26004648C8 /* CreatePaymentSourceSetupIntentClientSecret+ConstructorTests.swift */,
D6534D3922E7878D00E9D279 /* CreditCard+Utils.swift */,
D612A21822F0ED5A007F7FD9 /* CreditCard+UtilsTests.swift */,
8A4E954424525AC900A578CF /* CreditCardType+ImageName.swift */,
Expand Down Expand Up @@ -6493,6 +6505,7 @@
A7ED1F451E831BA200BFFA01 /* MockBundle.swift */,
774D98DB23B1520D00FC81C2 /* MockOptimizelyClient.swift */,
1611EF6823B275700051CDCC /* MockUUID.swift */,
1923770928DA2AE300F68635 /* Stripe+PaymentMethod.swift */,
A7ED1F461E831BA200BFFA01 /* TestCase.swift */,
A7ED1F471E831BA200BFFA01 /* XCTestCase+AppEnvironment.swift */,
);
Expand Down Expand Up @@ -7172,6 +7185,8 @@
D755ECAC23216C0D0096F189 /* CreateBackingInputTests.swift */,
D75DACA7221DC4340027F478 /* CreatePasswordInput.swift */,
D79CF32D219CE51A00ECB73A /* CreatePaymentSourceInput.swift */,
194154CD28D8ED69004648C8 /* CreatePaymentSourceSetupIntentInput.swift */,
194154D228D928C9004648C8 /* CreatePaymentSourceSetupIntentInputTests.swift */,
19047FBC2889AA6400BDD1A8 /* CreateSetupIntentInput.swift */,
19047FC42889B52100BDD1A8 /* CreateSetupIntentInputTests.swift */,
D6B686E82191FE5E005F5DA7 /* EmptyInput.swift */,
Expand Down Expand Up @@ -7883,6 +7898,7 @@
9D8772131D19E84E003A4E96 /* ProjectActivityLaunchCellViewModel.swift in Sources */,
774F8D5D22B1B14100A1ACD5 /* FeatureFlagToolsViewModel.swift in Sources */,
A7F441CF1D005A9400FE6FC5 /* MessageThreadCellViewModel.swift in Sources */,
194154D128D8FBAA004648C8 /* CreatePaymentSourceSetupIntentClientSecret+Constructor.swift in Sources */,
D69C552B239EFA6C00B0987A /* ActivityErroredBackingsCellViewModel.swift in Sources */,
A7F441CB1D005A9400FE6FC5 /* MessagesSearchViewModel.swift in Sources */,
A7CC14651D00E81B00035C52 /* FriendsSource.swift in Sources */,
Expand Down Expand Up @@ -8158,6 +8174,7 @@
061645AE26697D55007D8D96 /* CommentRepliesViewModelTests.swift in Sources */,
A7ED1F281E830FDC00BFFA01 /* CircleAvatarImageViewTests.swift in Sources */,
A7ED1FB81E831C5C00BFFA01 /* BackingCellViewModelTests.swift in Sources */,
1923770A28DA2AE300F68635 /* Stripe+PaymentMethod.swift in Sources */,
A7ED1FFD1E831C5C00BFFA01 /* ProjectUpdatesViewModelTests.swift in Sources */,
A7ED1F341E830FDC00BFFA01 /* SharedFunctionsTests.swift in Sources */,
D6B4EFFE21079C870079159D /* SettingsNewslettersViewModelTests.swift in Sources */,
Expand Down Expand Up @@ -8293,6 +8310,7 @@
A7ED1F291E830FDC00BFFA01 /* EnvironmentTests.swift in Sources */,
A7ED1F2E1E830FDC00BFFA01 /* LaunchedCountriesTests.swift in Sources */,
3705CF48222EE77F0025D37E /* EnvironmentVariablesTests.swift in Sources */,
194154D628D931AA004648C8 /* CreatePaymentSourceSetupIntentClientSecret+ConstructorTests.swift in Sources */,
D710ADFD2441172100DC7199 /* PledgeViewCTAContainerViewModelTests.swift in Sources */,
94114D8826547F620063E8F6 /* CommentCellViewModelTests.swift in Sources */,
D6B4D9BF209B88F3002C7B68 /* PushNotificationDialogTests.swift in Sources */,
Expand Down Expand Up @@ -8882,6 +8900,7 @@
D0158A2A1EEB30A2006E7684 /* SurveyResponseTemplates.swift in Sources */,
D6ED38A21F796FE5006CAAE9 /* GraphCategoriesEnvelopeTemplate.swift in Sources */,
D01588911EEB2ED7006E7684 /* DiscoveryEnvelopeLenses.swift in Sources */,
194154CE28D8ED69004648C8 /* CreatePaymentSourceSetupIntentInput.swift in Sources */,
D01588DF1EEB2ED7006E7684 /* User.StatsLenses.swift in Sources */,
06F7BE1826B31A020094BF37 /* UserCreditCards+UserFragment.swift in Sources */,
D7417CD620BF12F4004DABA6 /* ExportDataEnvelope.swift in Sources */,
Expand Down Expand Up @@ -9151,6 +9170,7 @@
8AC3E05E2697ABEF00168BF8 /* Project.Country+CountryFragmentTests.swift in Sources */,
066C0AE126CB11BD0048F4D8 /* ClearUserUnseenActivityMutationTemplate.swift in Sources */,
D0D10C131EEB4550005EBAD0 /* Project.PhotoTests.swift in Sources */,
194154D328D928C9004648C8 /* CreatePaymentSourceSetupIntentInputTests.swift in Sources */,
8AF34C762342D1D2000B211D /* UpdateBackingInputTests.swift in Sources */,
D0D10C191EEB4550005EBAD0 /* RewardTests.swift in Sources */,
D0D10C101EEB4550005EBAD0 /* MessageTests.swift in Sources */,
Expand Down
17 changes: 17 additions & 0 deletions KsApi/MockService.swift
Expand Up @@ -16,6 +16,8 @@

fileprivate let addNewCreditCardResult: Result<CreatePaymentSourceEnvelope, ErrorEnvelope>?

fileprivate let addPaymentSheetPaymentSourceResult: Result<CreatePaymentSourceEnvelope, ErrorEnvelope>?

fileprivate let cancelBackingResult: Result<EmptyResponseEnvelope, ErrorEnvelope>?

fileprivate let changeCurrencyResult: Result<EmptyResponseEnvelope, ErrorEnvelope>?
Expand Down Expand Up @@ -205,6 +207,7 @@
buildVersion: String = "1",
deviceIdentifier: String = "DEADBEEF-DEAD-BEEF-DEAD-DEADBEEFBEEF",
addNewCreditCardResult: Result<CreatePaymentSourceEnvelope, ErrorEnvelope>? = nil,
addPaymentSheetPaymentSourceResult: Result<CreatePaymentSourceEnvelope, ErrorEnvelope>? = nil,
apolloClient: ApolloClientType? = nil,
cancelBackingResult: Result<EmptyResponseEnvelope, ErrorEnvelope>? = nil,
changeEmailResult: Result<EmptyResponseEnvelope, ErrorEnvelope>? = nil,
Expand Down Expand Up @@ -309,6 +312,8 @@

self.addNewCreditCardResult = addNewCreditCardResult

self.addPaymentSheetPaymentSourceResult = addPaymentSheetPaymentSourceResult

self.apolloClient = apolloClient ?? MockGraphQLClient.shared.client

self.cancelBackingResult = cancelBackingResult
Expand Down Expand Up @@ -501,6 +506,18 @@
return client.performWithResult(mutation: mutation, result: self.addNewCreditCardResult)
}

public func addPaymentSheetPaymentSource(input: CreatePaymentSourceSetupIntentInput)
-> SignalProducer<CreatePaymentSourceEnvelope, ErrorEnvelope> {
guard let client = self.apolloClient else {
return .empty
}

let mutation = GraphAPI
.CreatePaymentSourceMutation(input: GraphAPI.CreatePaymentSourceInput.from(input))

return client.performWithResult(mutation: mutation, result: self.addPaymentSheetPaymentSourceResult)
}

public func cancelBacking(input: CancelBackingInput)
-> SignalProducer<EmptyResponseEnvelope, ErrorEnvelope> {
guard let client = self.apolloClient else {
Expand Down
8 changes: 8 additions & 0 deletions KsApi/Service.swift
Expand Up @@ -98,6 +98,14 @@ public struct Service: ServiceType {
.flatMap(CreatePaymentSourceEnvelope.producer(from:))
}

public func addPaymentSheetPaymentSource(input: CreatePaymentSourceSetupIntentInput)
-> SignalProducer<CreatePaymentSourceEnvelope, ErrorEnvelope> {
return GraphQL.shared.client
.perform(mutation: GraphAPI
.CreatePaymentSourceMutation(input: GraphAPI.CreatePaymentSourceInput.from(input)))
.flatMap(CreatePaymentSourceEnvelope.producer(from:))
}

public func cancelBacking(input: CancelBackingInput)
-> SignalProducer<EmptyResponseEnvelope, ErrorEnvelope> {
return GraphQL.shared.client
Expand Down
4 changes: 4 additions & 0 deletions KsApi/ServiceType.swift
Expand Up @@ -82,6 +82,10 @@ public protocol ServiceType {
func addNewCreditCard(input: CreatePaymentSourceInput) ->
SignalProducer<CreatePaymentSourceEnvelope, ErrorEnvelope>

/// Adds a new Stripe payment source to users' payment methods
func addPaymentSheetPaymentSource(input: CreatePaymentSourceSetupIntentInput) ->
SignalProducer<CreatePaymentSourceEnvelope, ErrorEnvelope>

/// Deletes a payment method
func deletePaymentMethod(input: PaymentSourceDeleteInput) ->
SignalProducer<DeletePaymentMethodEnvelope, ErrorEnvelope>
Expand Down
Expand Up @@ -9,4 +9,11 @@ extension GraphAPI.CreatePaymentSourceInput {
reusable: input.reusable
)
}

static func from(_ input: CreatePaymentSourceSetupIntentInput) -> GraphAPI.CreatePaymentSourceInput {
return GraphAPI.CreatePaymentSourceInput(
reusable: input.reuseable,
intentClientSecret: input.intentClientSecret
)
}
}
Expand Up @@ -17,4 +17,13 @@ class GraphAPI_CreatePaymentSourceInput_CreatePaymentSourceInputTests: XCTestCas
XCTAssertEqual(graphInput.stripeToken, input.stripeToken)
XCTAssertEqual(graphInput.stripeCardId, input.stripeCardId)
}

func testPaymentSheetPaymentSourceInputCreation_WithValidData_Success() {
let input = CreatePaymentSourceSetupIntentInput(intentClientSecret: "xyz", reuseable: true)

let graphInput = GraphAPI.CreatePaymentSourceInput.from(input)

XCTAssertEqual(graphInput.intentClientSecret, input.intentClientSecret)
XCTAssertEqual(graphInput.reusable, input.reuseable)
}
}
3 changes: 3 additions & 0 deletions KsApi/models/templates/CreatePaymentSourceTemplate.swift
Expand Up @@ -4,4 +4,7 @@ extension CreatePaymentSourceEnvelope {
internal static let paymentSourceSuccessTemplate = CreatePaymentSourceEnvelope(
createPaymentSource: .init(isSuccessful: true, paymentSource: UserCreditCards.amex)
)
internal static let paymentSourceFailureTemplate = CreatePaymentSourceEnvelope(
createPaymentSource: .init(isSuccessful: false, paymentSource: UserCreditCards.amex)
)
}
16 changes: 16 additions & 0 deletions KsApi/mutations/inputs/CreatePaymentSourceSetupIntentInput.swift
@@ -0,0 +1,16 @@
public struct CreatePaymentSourceSetupIntentInput: GraphMutationInput {
let intentClientSecret: String
let reuseable: Bool

public init(intentClientSecret: String, reuseable: Bool) {
self.intentClientSecret = intentClientSecret
self.reuseable = reuseable
}

public func toInputDictionary() -> [String: Any] {
return [
"intentClientSecret": self.intentClientSecret,
"reusable": self.reuseable
]
}
}
@@ -0,0 +1,15 @@
@testable import KsApi
import Prelude
import XCTest

final class CreatePaymentSourceSetupIntentInputTests: XCTestCase {
func testCreatePaymentSourceSetupIntentInputDictionary_WithValue_Success() {
let createSetupIntentInput =
CreatePaymentSourceSetupIntentInput(intentClientSecret: "UHJvamVjdC0yMzEyODc5ODc", reuseable: false)

let input = createSetupIntentInput.toInputDictionary()

XCTAssertEqual(input["intentClientSecret"] as? String, "UHJvamVjdC0yMzEyODc5ODc")
XCTAssertEqual(input["reusable"] as? Bool, false)
}
}
@@ -0,0 +1,10 @@
import KsApi

extension CreatePaymentSourceSetupIntentInput {
internal static func input(
fromIntentClientSecret token: String,
reuseable: Bool
) -> CreatePaymentSourceSetupIntentInput {
return CreatePaymentSourceSetupIntentInput(intentClientSecret: token, reuseable: reuseable)
}
}
@@ -0,0 +1,12 @@
@testable import KsApi
@testable import Library
import XCTest

final class CreatePaymentSourceSetupIntentClientSecret_ConstructorTests: TestCase {
func testCreatePaymentSourceSetupIntentInput_Reusable() {
let input = CreatePaymentSourceSetupIntentInput.input(fromIntentClientSecret: "xyz", reuseable: true)

XCTAssertEqual(input.intentClientSecret, "xyz")
XCTAssertEqual(input.reuseable, true)
}
}
30 changes: 30 additions & 0 deletions Library/TestHelpers/Stripe+PaymentMethod.swift
@@ -0,0 +1,30 @@
@testable import Stripe

extension STPPaymentMethod {
static let visaStripePaymentMethod = STPPaymentMethod.decodedObject(fromAPIResponse: [
"id": "_randomID123",
"card": [
"brand": "visa",
"last4": "1234"
],
"type": "card"
])

static let amexStripePaymentMethod = STPPaymentMethod.decodedObject(fromAPIResponse: [
"id": "_randomID123",
"card": [
"brand": "amex",
"last4": "1234"
],
"type": "card"
])

static let sampleStringPaymentOption: (STPPaymentMethod) -> PaymentSheet.PaymentOption = { paymentMethod in
PaymentSheet.PaymentOption.saved(paymentMethod: paymentMethod)
}

static let samplePaymentOptionsDisplayData: (PaymentSheet.PaymentOption) -> PaymentSheet.FlowController
.PaymentOptionDisplayData = { paymentOption in
PaymentSheet.FlowController.PaymentOptionDisplayData(paymentOption: paymentOption)
}
}

0 comments on commit e3cb02a

Please sign in to comment.