Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose billing details & PMT for FlowController #3165

Merged
merged 18 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## X.X.X
## x.x.x x-x-x
### PaymentSheet
* [Fixed] Fixed a few design issues on visionOS.
* [Added] Exposed BillingDetails and type on selected PaymentOption when dismissing FlowController's payment option selector
wooj-stripe marked this conversation as resolved.
Show resolved Hide resolved

## 23.20.0 2023-12-18
### PaymentSheet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,21 @@ struct PaymentSheetButtons: View {
}.padding(.horizontal)
HStack {
if let psfc = playgroundController.paymentSheetFlowController {
Button {
psFCOptionsIsPresented = true
} label: {
PaymentOptionView(paymentOptionDisplayData: playgroundController.paymentSheetFlowController?.paymentOption)
VStack {
Button {
psFCOptionsIsPresented = true
} label: {
PaymentOptionView(paymentOptionDisplayData: playgroundController.paymentSheetFlowController?.paymentOption)
}
.disabled(playgroundController.paymentSheetFlowController == nil)
Button {
psFCOptionsIsPresented = true
} label: {
PaymentOptionInfoView(paymentOptionDisplayData: playgroundController.paymentSheetFlowController?.paymentOption)
}
wooj-stripe marked this conversation as resolved.
Show resolved Hide resolved
.disabled(playgroundController.paymentSheetFlowController == nil)
.padding()
}
.disabled(playgroundController.paymentSheetFlowController == nil)
.padding()
Button {
playgroundController.didTapShippingAddressButton()
} label: {
Expand Down Expand Up @@ -336,10 +344,72 @@ struct PaymentOptionView: View {
// "Payment method-Payment method". We'll set it on a single View instead.
.accessibility(identifier: "Payment method")
.foregroundColor(.primary)
}
}
}

struct PaymentOptionInfoView: View {
let paymentOptionDisplayData: PaymentSheet.FlowController.PaymentOptionDisplayData?

var body: some View {
VStack {
if let paymentMethodType = paymentOptionDisplayData?.paymentMethodType {
Text(paymentMethodType)
.font(.caption)
.foregroundColor(.primary)
}
if let billingDetails = paymentOptionDisplayData?.billingDetails {
BillingDetailsView(billingDetails: billingDetails)
.font(.caption)
.foregroundColor(.primary)

}
}
}
}

struct BillingDetailsView: View {
let billingDetails: PaymentSheet.BillingDetails

var body: some View {
VStack {
if let name = billingDetails.name {
Text(name)
.accessibility(identifier: "Name")
}
if let email = billingDetails.email {
Text(email)
.accessibility(identifier: "Email")
}
if let phone = billingDetails.phone {
Text(phone)
.accessibility(identifier: "Phone")
}
if let line1 = billingDetails.address.line1 {
Text(line1)
.accessibility(identifier: "Line1")
}
if let line2 = billingDetails.address.line2 {
Text(line2)
.accessibility(identifier: "Line2")
}
if let city = billingDetails.address.city {
Text(city)
.accessibility(identifier: "City")
}
if let state = billingDetails.address.state {
Text(state)
.accessibility(identifier: "State")
}
if let postalCode = billingDetails.address.postalCode {
Text(postalCode)
.accessibility(identifier: "PostalCode")
}
if let country = billingDetails.address.country {
Text(country)
.accessibility(identifier: "Country")
}
}
.padding()
.foregroundColor(.black)
.cornerRadius(6)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ class PaymentSheetBillingCollectionUITestCase: XCTestCase {
var checkoutButton: XCUIElement { app.buttons["Present PaymentSheet"] }
var payButton: XCUIElement { app.buttons["Pay $50.99"] }
var successText: XCUIElement { app.staticTexts["Success!"] }

// FlowController specific buttons
var paymentMethodSelectorNoneButton: XCUIElement { app.buttons["None"] }
var confirmButton: XCUIElement { app.buttons["Confirm"] }
var continueButton: XCUIElement { app.buttons["Continue"] }

}

class PaymentSheetBillingCollectionUICardTests: PaymentSheetBillingCollectionUITestCase {
Expand Down Expand Up @@ -116,6 +122,68 @@ class PaymentSheetBillingCollectionUICardTests: PaymentSheetBillingCollectionUIT
XCTAssertTrue(successText.waitForExistence(timeout: 10.0))
}

func testCard_AllFields_flowController_WithDefaults() throws {

var settings = PaymentSheetTestPlaygroundSettings.defaultValues()
settings.customerMode = .guest
settings.uiStyle = .flowController
settings.currency = .usd
settings.merchantCountryCode = .US
settings.applePayEnabled = .off
settings.apmsEnabled = .off
settings.linkEnabled = .off
settings.defaultBillingAddress = .on
settings.attachDefaults = .on
settings.collectName = .always
settings.collectEmail = .always
settings.collectPhone = .always
settings.collectAddress = .full
loadPlayground(
app,
settings
)
paymentMethodSelectorNoneButton.tap()

let card = try XCTUnwrap(scroll(collectionView: app.collectionViews.firstMatch, toFindCellWithId: "card"))
card.tap()

XCTAssertTrue(cardInfoField.waitForExistence(timeout: 10.0))
XCTAssertTrue(contactInfoField.exists)
XCTAssertEqual(emailField.value as? String, "foo@bar.com")
XCTAssertEqual(phoneField.value as? String, "(310) 555-1234")
XCTAssertEqual(nameOnCardField.value as? String, "Jane Doe")
XCTAssertTrue(billingAddressField.exists)
XCTAssertEqual(countryField.value as? String, "United States")
XCTAssertEqual(line1Field.value as? String, "510 Townsend St.")
XCTAssertEqual(line2Field.value as? String, "")
XCTAssertEqual(cityField.value as? String, "San Francisco")
XCTAssertEqual(stateField.value as? String, "California")
XCTAssertEqual(zipField.value as? String, "94102")

let numberField = app.textFields["Card number"]
numberField.forceTapWhenHittableInTestCase(self)
app.typeText("4242424242424242")
app.typeText("1228") // Expiry
app.typeText("123") // CVC
app.toolbars.buttons["Done"].tap() // Dismiss keyboard.

// Dismiss FlowController payment method selector
continueButton.tap()

XCTAssertTrue(app.staticTexts["card"].waitForExistence(timeout: 10.0))
XCTAssertTrue(app.staticTexts["Jane Doe"].waitForExistence(timeout: 10.0))
XCTAssertTrue(app.staticTexts["foo@bar.com"].waitForExistence(timeout: 10.0))
XCTAssertTrue(app.staticTexts["+13105551234"].waitForExistence(timeout: 10.0))
XCTAssertTrue(app.staticTexts["510 Townsend St."].waitForExistence(timeout: 10.0))
XCTAssertTrue(app.staticTexts["San Francisco"].waitForExistence(timeout: 10.0))
XCTAssertTrue(app.staticTexts["CA"].waitForExistence(timeout: 10.0))
XCTAssertTrue(app.staticTexts["94102"].waitForExistence(timeout: 10.0))
XCTAssertTrue(app.staticTexts["US"].waitForExistence(timeout: 10.0))

confirmButton.tap()
XCTAssertTrue(successText.waitForExistence(timeout: 10.0))
}

func testCard_OnlyCardInfo_WithDefaults() throws {

var settings = PaymentSheetTestPlaygroundSettings.defaultValues()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,19 @@ extension PaymentSheet.LinkConfirmOption {
}
}

var billingDetails: STPPaymentMethodBillingDetails? {
switch self {
case .wallet:
return nil
case .signUp(_, _, _, let paymentMethodParams):
return paymentMethodParams.billingDetails
case .withPaymentDetails:
return nil
case .withPaymentMethodParams(_, let paymentMethodParams):
return paymentMethodParams.billingDetails
case .withPaymentMethod(let paymentMethod):
return paymentMethod.billingDetails
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,11 @@ extension PaymentSheet {
public var address: Address = Address()

/// The customer's email
/// - Note: The value set is displayed in the payment sheet as-is. Depending on the payment method, the customer may be required to edit this value.
/// - Note: When used with defaultBillingDetails, the value set is displayed in the payment sheet as-is. Depending on the payment method, the customer may be required to edit this value.
public var email: String?

/// The customer's full name
/// - Note: The value set is displayed in the payment sheet as-is. Depending on the payment method, the customer may be required to edit this value.
/// - Note: When used with defaultBillingDetails, the value set is displayed in the payment sheet as-is. Depending on the payment method, the customer may be required to edit this value.
public var name: String?

/// The customer's phone number without formatting (e.g. 5551234567)
Expand Down Expand Up @@ -444,3 +444,18 @@ extension PaymentSheet.Configuration {
|| billingDetailsCollectionConfiguration.address == .full
}
}

extension STPPaymentMethodBillingDetails {
func toPaymentSheetBillingDetails() -> PaymentSheet.BillingDetails {
let address = PaymentSheet.Address(city: self.address?.city,
country: self.address?.country,
line1: self.address?.line1,
line2: self.address?.line2,
postalCode: self.address?.postalCode,
state: self.address?.state)
return PaymentSheet.BillingDetails(address: address,
email: self.email,
name: self.name,
phone: self.phone)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,38 @@ extension PaymentSheet {
/// A user facing string representing the payment method; e.g. "Apple Pay" or "····4242" for a card
public let label: String

/// The billing details associated with the customer's desired payment option
wooj-stripe marked this conversation as resolved.
Show resolved Hide resolved
public let billingDetails: PaymentSheet.BillingDetails?

/// A string representation of the customer's desired payment option
wooj-stripe marked this conversation as resolved.
Show resolved Hide resolved
/// - If this is a Stripe payment method, see https://stripe.com/docs/api/payment_methods/object#payment_method_object-type for possible values.
/// - If this is an external payment method, see https://stripe.com/docs/payments/external-payment-methods?platform=ios#available-external-payment-methods for possible values.
/// - If this is Apple Pay, the value is "apple_pay"
public let paymentMethodType: String

init(paymentOption: PaymentOption) {
image = paymentOption.makeIcon(updateImageHandler: nil)
switch paymentOption {
case .applePay:
label = String.Localized.apple_pay
paymentMethodType = "apple_pay"
billingDetails = nil
case .saved(let paymentMethod):
label = paymentMethod.paymentSheetLabel
paymentMethodType = paymentMethod.type.identifier
billingDetails = paymentMethod.billingDetails?.toPaymentSheetBillingDetails()
case .new(let confirmParams):
label = confirmParams.paymentSheetLabel
paymentMethodType = confirmParams.paymentMethodType.identifier
billingDetails = confirmParams.paymentMethodParams.billingDetails?.toPaymentSheetBillingDetails()
case .link(let option):
label = option.paymentSheetLabel
case .external(let paymentMethod, _):
paymentMethodType = STPPaymentMethodType.link.identifier
billingDetails = option.billingDetails?.toPaymentSheetBillingDetails()
case .external(let paymentMethod, let stpBillingDetails):
label = paymentMethod.label
paymentMethodType = paymentMethod.type
billingDetails = stpBillingDetails.toPaymentSheetBillingDetails()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,28 @@ class PaymentSheetConfigurationTests: XCTestCase {
configuration.billingDetailsCollectionConfiguration.phone = .always
XCTAssert(configuration.isUsingBillingAddressCollection())
}

func testSTPPaymentMethodBillingDetailsToPaymentSheetBililngDetails() {
wooj-stripe marked this conversation as resolved.
Show resolved Hide resolved
var billingDetails = STPPaymentMethodBillingDetails()
billingDetails.name = "Jane Doe"
billingDetails.email = "janedoe@test.com"
billingDetails.phone = "+18885551234"
billingDetails.address = STPPaymentMethodAddress()
billingDetails.address?.line1 = "123 Main Street"
billingDetails.address?.line2 = ""
billingDetails.address?.city = "San Francisco"
billingDetails.address?.state = "California"
billingDetails.address?.country = "US"

let psBillingDetails = billingDetails.toPaymentSheetBillingDetails()
wooj-stripe marked this conversation as resolved.
Show resolved Hide resolved

XCTAssertEqual(psBillingDetails.name, "Jane Doe")
XCTAssertEqual(psBillingDetails.email, "janedoe@test.com")
XCTAssertEqual(psBillingDetails.phone, "+18885551234")
wooj-stripe marked this conversation as resolved.
Show resolved Hide resolved
XCTAssertEqual(psBillingDetails.address.line1, "123 Main Street")
XCTAssertEqual(psBillingDetails.address.line2, "")
XCTAssertEqual(psBillingDetails.address.city, "San Francisco")
XCTAssertEqual(psBillingDetails.address.state, "California")
XCTAssertEqual(psBillingDetails.address.country, "US")
}
}