add standalone shipping address view controller

This commit is contained in:
Ben Guo 2016-09-13 11:51:42 +01:00
parent 84911420c3
commit 80dc7f0c8d
27 changed files with 1222 additions and 78 deletions

View File

@ -9,7 +9,7 @@
import UIKit
import Stripe
class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
class CheckoutViewController: UIViewController, STPPaymentContextDelegate, STPShippingAddressViewControllerDelegate {
// 1) To get started with this demo, first head to https://dashboard.stripe.com/account/apikeys
// and copy your "Test Publishable Key" (it looks like pk_test_abcdef) into the line below.
@ -33,11 +33,15 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
let theme: STPTheme
let paymentRow: CheckoutRowView
let shippingRow: CheckoutRowView
let totalRow: CheckoutRowView
let buyButton: BuyButton
let rowHeight: CGFloat = 44
let productImage = UILabel()
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
let numberFormatter: NumberFormatter
let shippingString: String
let shippingVC: STPShippingAddressViewController
var product = ""
var paymentInProgress: Bool = false {
didSet {
@ -60,11 +64,6 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
self.product = product
self.productImage.text = product
self.theme = settings.theme
self.paymentRow = CheckoutRowView(title: "Payment", detail: "Select Payment",
theme: settings.theme)
self.totalRow = CheckoutRowView(title: "Total", detail: "", tappable: false,
theme: settings.theme)
self.buyButton = BuyButton(enabled: true, theme: settings.theme)
MyAPIClient.sharedClient.baseURLString = self.backendBaseURL
// This code is included here for the sake of readability, but in your application you should set up your configuration and theme earlier, preferably in your App Delegate.
@ -73,6 +72,8 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
config.appleMerchantIdentifier = self.appleMerchantID
config.companyName = self.companyName
config.requiredBillingAddressFields = settings.requiredBillingAddressFields
config.requiredShippingAddressFields = settings.requiredShippingAddressFields
config.shippingType = settings.shippingType
config.additionalPaymentMethods = settings.additionalPaymentMethods
config.smsAutofillDisabled = !settings.smsAutofillEnabled
@ -81,12 +82,37 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
theme: settings.theme)
let userInformation = STPUserInformation()
paymentContext.prefilledInformation = userInformation
paymentContext.paymentAmount = price
paymentContext.paymentCurrency = self.paymentCurrency
self.paymentContext = paymentContext
self.paymentRow = CheckoutRowView(title: "Payment", detail: "Select Payment",
theme: settings.theme)
var shippingString = "Contact"
if config.requiredShippingAddressFields.contains(.postalAddress) {
shippingString = config.shippingType == .shipping ? "Shipping" : "Delivery"
}
self.shippingString = shippingString
self.shippingRow = CheckoutRowView(title: self.shippingString,
detail: "Enter \(self.shippingString) Info",
theme: settings.theme)
self.totalRow = CheckoutRowView(title: "Total", detail: "", tappable: false,
theme: settings.theme)
self.buyButton = BuyButton(enabled: true, theme: settings.theme)
var localeComponents: [String: String] = [
NSLocale.Key.currencyCode.rawValue: self.paymentCurrency,
]
localeComponents[NSLocale.Key.languageCode.rawValue] = NSLocale.preferredLanguages.first
let localeID = NSLocale.localeIdentifier(fromComponents: localeComponents)
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: localeID)
numberFormatter.numberStyle = .currency
numberFormatter.usesGroupingSeparator = true
self.numberFormatter = numberFormatter
let shippingVC = STPShippingAddressViewController()
self.shippingVC = shippingVC
super.init(nibName: nil, bundle: nil)
shippingVC.delegate = self
self.paymentContext.delegate = self
paymentContext.hostViewController = self
}
@ -106,15 +132,20 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
self.productImage.font = UIFont.systemFont(ofSize: 70)
self.view.addSubview(self.totalRow)
self.view.addSubview(self.paymentRow)
self.view.addSubview(self.shippingRow)
self.view.addSubview(self.productImage)
self.view.addSubview(self.buyButton)
self.view.addSubview(self.activityIndicator)
self.activityIndicator.alpha = 0
self.buyButton.addTarget(self, action: #selector(didTapBuy), for: .touchUpInside)
self.totalRow.detail = "$\(self.paymentContext.paymentAmount/100).00"
self.totalRow.detail = self.numberFormatter.string(from: NSNumber(value: Float(self.paymentContext.paymentAmount)/100))!
self.paymentRow.onTap = { [weak self] _ in
self?.paymentContext.pushPaymentMethodsViewController()
}
self.shippingRow.onTap = { [weak self] _ in
guard let strongSelf = self else { return }
strongSelf.navigationController?.pushViewController(strongSelf.shippingVC, animated: true)
}
}
override func viewDidLayoutSubviews() {
@ -122,11 +153,13 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
let width = self.view.bounds.width
self.productImage.sizeToFit()
self.productImage.center = CGPoint(x: width/2.0,
y: self.productImage.bounds.height/2.0 + rowHeight)
y: self.productImage.bounds.height/2.0 + rowHeight)
self.paymentRow.frame = CGRect(x: 0, y: self.productImage.frame.maxY + rowHeight,
width: width, height: rowHeight)
self.totalRow.frame = CGRect(x: 0, y: self.paymentRow.frame.maxY,
width: width, height: rowHeight)
width: width, height: rowHeight)
self.shippingRow.frame = CGRect(x: 0, y: self.paymentRow.frame.maxY,
width: width, height: rowHeight)
self.totalRow.frame = CGRect(x: 0, y: self.shippingRow.frame.maxY,
width: width, height: rowHeight)
self.buyButton.frame = CGRect(x: 0, y: 0, width: 88, height: 44)
self.buyButton.center = CGPoint(x: width/2.0, y: self.totalRow.frame.maxY + rowHeight*1.5)
self.activityIndicator.center = self.buyButton.center
@ -136,6 +169,8 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
self.paymentInProgress = true
self.paymentContext.requestPayment()
}
// MARK: STPPaymentContextDelegate
func paymentContext(_ paymentContext: STPPaymentContext, didCreatePaymentResult paymentResult: STPPaymentResult, completion: @escaping STPErrorBlock) {
MyAPIClient.sharedClient.completeCharge(paymentResult, amount: self.paymentContext.paymentAmount,
@ -162,8 +197,6 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
self.present(alertController, animated: true, completion: nil)
}
// MARK: STPPaymentContextDelegate
func paymentContextDidChange(_ paymentContext: STPPaymentContext) {
self.paymentRow.loading = paymentContext.loading
if let paymentMethod = paymentContext.selectedPaymentMethod {
@ -172,6 +205,7 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
else {
self.paymentRow.detail = "Select Payment"
}
self.totalRow.detail = self.numberFormatter.string(from: NSNumber(value: Float(self.paymentContext.paymentAmount)/100))!
}
func paymentContext(_ paymentContext: STPPaymentContext, didFailToLoadWithError error: Error) {
@ -193,4 +227,36 @@ class CheckoutViewController: UIViewController, STPPaymentContextDelegate {
self.present(alertController, animated: true, completion: nil)
}
// MARK: STPShippingAddressViewControllerDelegate
func shippingAddressViewControllerDidCancel(_ addressViewController: STPShippingAddressViewController) {
let _ = self.navigationController?.popViewController(animated: true)
}
func shippingAddressViewController(_ addressViewController: STPShippingAddressViewController, didEnter address: STPAddress, completion: @escaping STPShippingMethodsCompletionBlock) {
let shippingMethod1 = PKShippingMethod()
shippingMethod1.amount = 0
shippingMethod1.label = "UPS Ground"
shippingMethod1.detail = "Arrives in 3-5 days"
shippingMethod1.identifier = "123"
let shippingMethod2 = PKShippingMethod()
shippingMethod2.amount = 5.99
shippingMethod2.label = "FedEx"
shippingMethod2.detail = "Arrives tomorrow"
shippingMethod2.identifier = "456"
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
completion(nil, [shippingMethod1, shippingMethod2])
}
}
func shippingAddressViewController(_ addressViewController: STPShippingAddressViewController, didFinishWith address: STPAddress, shippingMethod method: PKShippingMethod?) {
if let shippingMethod = method {
self.shippingRow.detail = shippingMethod.label
}
else {
self.shippingRow.detail = "Enter \(self.shippingString) Info"
}
self.shippingVC.dismiss(withHostViewController: self)
}
}

View File

@ -13,6 +13,8 @@ struct Settings {
let theme: STPTheme
let additionalPaymentMethods: STPPaymentMethodType
let requiredBillingAddressFields: STPBillingAddressFields
let requiredShippingAddressFields: PKAddressField
let shippingType: STPShippingType
let smsAutofillEnabled: Bool
}
@ -21,18 +23,24 @@ class SettingsViewController: UITableViewController {
return Settings(theme: self.theme.stpTheme,
additionalPaymentMethods: self.applePay.enabled ? .all : STPPaymentMethodType(),
requiredBillingAddressFields: self.requiredBillingAddressFields.stpBillingAddressFields,
requiredShippingAddressFields: self.requiredShippingAddressFields.pkAddressFields,
shippingType: self.shippingType.stpShippingType,
smsAutofillEnabled: self.smsAutofill.enabled)
}
fileprivate var theme: Theme = .Default
fileprivate var applePay: Switch = .Enabled
fileprivate var requiredBillingAddressFields: RequiredBillingAddressFields = .None
fileprivate var requiredShippingAddressFields: RequiredShippingAddressFields = .PostalAddressPhone
fileprivate var shippingType: ShippingType = .Shipping
fileprivate var smsAutofill: Switch = .Enabled
fileprivate enum Section: String {
case Theme = "Theme"
case ApplePay = "Apple Pay"
case RequiredBillingAddressFields = "Required Billing Address Fields"
case RequiredShippingAddressFields = "Required Shipping Address Fields"
case ShippingType = "Shipping Type"
case SMSAutofill = "SMS Autofill"
case Session = "Session"
@ -41,7 +49,9 @@ class SettingsViewController: UITableViewController {
case 0: self = .Theme
case 1: self = .ApplePay
case 2: self = .RequiredBillingAddressFields
case 3: self = .SMSAutofill
case 3: self = .RequiredShippingAddressFields
case 4: self = .ShippingType
case 5: self = .SMSAutofill
default: self = .Session
}
}
@ -125,6 +135,64 @@ class SettingsViewController: UITableViewController {
}
}
private enum RequiredShippingAddressFields: String {
case None = "None"
case Phone = "Phone"
case Email = "Email"
case NameEmail = "(Name|Email)"
case PostalAddress = "PostalAddress"
case PostalAddressPhone = "(PostalAddress|Phone)"
case All = "All"
init(row: Int) {
switch row {
case 0: self = .None
case 1: self = .Phone
case 2: self = .Email
case 3: self = .NameEmail
case 4: self = .PostalAddress
case 5: self = .PostalAddressPhone
default: self = .All
}
}
var pkAddressFields: PKAddressField {
switch self {
case .None: return []
case .Phone: return .phone
case .Email: return .email
case .NameEmail:
if #available(iOS 8.3, *) {
return [.name, .email]
} else {
return [.email]
}
case .PostalAddress: return .postalAddress
case .PostalAddressPhone: return [.postalAddress, .phone]
case .All: return .all
}
}
}
private enum ShippingType: String {
case Shipping = "Shipping"
case Delivery = "Delivery"
init(row: Int) {
switch row {
case 0: self = .Shipping
default: self = .Delivery
}
}
var stpShippingType: STPShippingType {
switch self {
case .Shipping: return .shipping
case .Delivery: return .delivery
}
}
}
convenience init() {
self.init(style: .grouped)
}
@ -140,7 +208,7 @@ class SettingsViewController: UITableViewController {
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 5
return 7
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@ -148,6 +216,8 @@ class SettingsViewController: UITableViewController {
case .Theme: return 3
case .ApplePay: return 2
case .RequiredBillingAddressFields: return 3
case .RequiredShippingAddressFields: return 7
case .ShippingType: return 2
case .SMSAutofill: return 2
case .Session: return 1
}
@ -172,6 +242,14 @@ class SettingsViewController: UITableViewController {
let value = RequiredBillingAddressFields(row: (indexPath as NSIndexPath).row)
cell.textLabel?.text = value.rawValue
cell.accessoryType = value == self.requiredBillingAddressFields ? .checkmark : .none
case .RequiredShippingAddressFields:
let value = RequiredShippingAddressFields(row: indexPath.row)
cell.textLabel?.text = value.rawValue
cell.accessoryType = value == self.requiredShippingAddressFields ? .checkmark : .none
case .ShippingType:
let value = ShippingType(row: indexPath.row)
cell.textLabel?.text = value.rawValue
cell.accessoryType = value == self.shippingType ? .checkmark : .none
case .SMSAutofill:
let value = Switch(row: (indexPath as NSIndexPath).row)
cell.textLabel?.text = value.rawValue
@ -192,6 +270,10 @@ class SettingsViewController: UITableViewController {
self.applePay = Switch(row: (indexPath as NSIndexPath).row)
case .RequiredBillingAddressFields:
self.requiredBillingAddressFields = RequiredBillingAddressFields(row: (indexPath as NSIndexPath).row)
case .RequiredShippingAddressFields:
self.requiredShippingAddressFields = RequiredShippingAddressFields(row: (indexPath as NSIndexPath).row)
case .ShippingType:
self.shippingType = ShippingType(row: (indexPath as NSIndexPath).row)
case .SMSAutofill:
self.smsAutofill = Switch(row: (indexPath as NSIndexPath).row)
case .Session:

View File

@ -433,6 +433,24 @@
C1363BB81D7633D800EB82B4 /* STPPaymentMethodTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C1363BB51D7633D800EB82B4 /* STPPaymentMethodTableViewCell.h */; };
C1363BB91D7633D800EB82B4 /* STPPaymentMethodTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C1363BB61D7633D800EB82B4 /* STPPaymentMethodTableViewCell.m */; };
C1363BBA1D7633D800EB82B4 /* STPPaymentMethodTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C1363BB61D7633D800EB82B4 /* STPPaymentMethodTableViewCell.m */; };
C15993231D8807930047950D /* stp_shipping_form.png in Resources */ = {isa = PBXBuildFile; fileRef = C15993201D8807930047950D /* stp_shipping_form.png */; };
C15993241D8807930047950D /* stp_shipping_form@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C15993211D8807930047950D /* stp_shipping_form@2x.png */; };
C15993251D8807930047950D /* stp_shipping_form@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C15993221D8807930047950D /* stp_shipping_form@3x.png */; };
C15993281D8808490047950D /* STPShippingAddressViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C15993261D8808490047950D /* STPShippingAddressViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
C159932A1D88084D0047950D /* STPShippingAddressViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C15993261D8808490047950D /* STPShippingAddressViewController.h */; settings = {ATTRIBUTES = (Public, ); }; };
C15993331D8808680047950D /* STPShippingAddressViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C159932C1D8808680047950D /* STPShippingAddressViewController.m */; };
C15993361D8808680047950D /* STPShippingMethodsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C159932F1D8808680047950D /* STPShippingMethodsViewController.h */; };
C15993371D8808680047950D /* STPShippingMethodsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C15993301D8808680047950D /* STPShippingMethodsViewController.m */; };
C15993381D8808680047950D /* STPShippingMethodTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C15993311D8808680047950D /* STPShippingMethodTableViewCell.h */; };
C15993391D8808680047950D /* STPShippingMethodTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C15993321D8808680047950D /* STPShippingMethodTableViewCell.m */; };
C159933A1D8808880047950D /* STPShippingAddressViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C159932C1D8808680047950D /* STPShippingAddressViewController.m */; };
C159933D1D8808970047950D /* STPShippingMethodsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C159932F1D8808680047950D /* STPShippingMethodsViewController.h */; };
C159933F1D88089B0047950D /* STPShippingMethodsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C15993301D8808680047950D /* STPShippingMethodsViewController.m */; };
C15993401D88089E0047950D /* STPShippingMethodTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C15993311D8808680047950D /* STPShippingMethodTableViewCell.h */; };
C15993411D8808A10047950D /* STPShippingMethodTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C15993321D8808680047950D /* STPShippingMethodTableViewCell.m */; };
C15993451D8829C00047950D /* stp_shipping_form.png in Resources */ = {isa = PBXBuildFile; fileRef = C15993201D8807930047950D /* stp_shipping_form.png */; };
C15993461D8829C00047950D /* stp_shipping_form@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C15993211D8807930047950D /* stp_shipping_form@2x.png */; };
C15993471D8829C00047950D /* stp_shipping_form@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = C15993221D8807930047950D /* stp_shipping_form@3x.png */; };
C16F66AB1CA21BAC006A21B5 /* STPFormTextFieldTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C16F66AA1CA21BAC006A21B5 /* STPFormTextFieldTest.m */; };
C1717DB11CC00ED60009CF4A /* STPAddress.h in Headers */ = {isa = PBXBuildFile; fileRef = C1080F471CBECF7B007B2D89 /* STPAddress.h */; settings = {ATTRIBUTES = (Public, ); }; };
C17A030D1CBEE7A2006C819F /* STPAddressFieldTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C17A030B1CBEE7A2006C819F /* STPAddressFieldTableViewCell.h */; };
@ -906,6 +924,15 @@
C1363BAE1D76337400EB82B4 /* stp_icon_checkmark@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_icon_checkmark@3x.png"; sourceTree = "<group>"; };
C1363BB51D7633D800EB82B4 /* STPPaymentMethodTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STPPaymentMethodTableViewCell.h; sourceTree = "<group>"; };
C1363BB61D7633D800EB82B4 /* STPPaymentMethodTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPPaymentMethodTableViewCell.m; sourceTree = "<group>"; };
C15993201D8807930047950D /* stp_shipping_form.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = stp_shipping_form.png; sourceTree = "<group>"; };
C15993211D8807930047950D /* stp_shipping_form@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_shipping_form@2x.png"; sourceTree = "<group>"; };
C15993221D8807930047950D /* stp_shipping_form@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "stp_shipping_form@3x.png"; sourceTree = "<group>"; };
C15993261D8808490047950D /* STPShippingAddressViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = STPShippingAddressViewController.h; path = PublicHeaders/STPShippingAddressViewController.h; sourceTree = "<group>"; };
C159932C1D8808680047950D /* STPShippingAddressViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPShippingAddressViewController.m; sourceTree = "<group>"; };
C159932F1D8808680047950D /* STPShippingMethodsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STPShippingMethodsViewController.h; sourceTree = "<group>"; };
C15993301D8808680047950D /* STPShippingMethodsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPShippingMethodsViewController.m; sourceTree = "<group>"; };
C15993311D8808680047950D /* STPShippingMethodTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = STPShippingMethodTableViewCell.h; sourceTree = "<group>"; };
C15993321D8808680047950D /* STPShippingMethodTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPShippingMethodTableViewCell.m; sourceTree = "<group>"; };
C16A4CDB1D36B19B001F46D2 /* MockSTPCheckoutAPIClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MockSTPCheckoutAPIClient.h; sourceTree = "<group>"; };
C16A4CDC1D36B19B001F46D2 /* MockSTPCheckoutAPIClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MockSTPCheckoutAPIClient.m; sourceTree = "<group>"; };
C16F66AA1CA21BAC006A21B5 /* STPFormTextFieldTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = STPFormTextFieldTest.m; sourceTree = "<group>"; };
@ -1111,6 +1138,9 @@
04E39F621CED3B0100AF3B96 /* stp_icon_chevron_right_small.png */,
04E39F631CED3B0100AF3B96 /* stp_icon_chevron_right_small@2x.png */,
04E39F641CED3B0100AF3B96 /* stp_icon_chevron_right_small@3x.png */,
C15993201D8807930047950D /* stp_shipping_form.png */,
C15993211D8807930047950D /* stp_shipping_form@2x.png */,
C15993221D8807930047950D /* stp_shipping_form@3x.png */,
);
path = Images;
sourceTree = "<group>";
@ -1323,6 +1353,12 @@
F1852F921D80B6EC00367C86 /* STPStringUtils.m */,
F1C7B8D21DBECF2400D9F6F0 /* STPDispatchFunctions.h */,
F1C7B8D11DBECF2400D9F6F0 /* STPDispatchFunctions.m */,
C15993261D8808490047950D /* STPShippingAddressViewController.h */,
C159932C1D8808680047950D /* STPShippingAddressViewController.m */,
C159932F1D8808680047950D /* STPShippingMethodsViewController.h */,
C15993301D8808680047950D /* STPShippingMethodsViewController.m */,
C15993311D8808680047950D /* STPShippingMethodTableViewCell.h */,
C15993321D8808680047950D /* STPShippingMethodTableViewCell.m */,
);
name = Stripe;
path = Tests/../Stripe;
@ -1489,6 +1525,7 @@
04F94DA11D229F12004FC826 /* STPAddressFieldTableViewCell.h in Headers */,
04EBC75A1B7533C300A0E6AE /* STPCardValidator.h in Headers */,
04F94DA61D229F27004FC826 /* STPCardTuple.h in Headers */,
C159933D1D8808970047950D /* STPShippingMethodsViewController.h in Headers */,
04F94DCD1D22A22F004FC826 /* UIViewController+Stripe_KeyboardAvoiding.h in Headers */,
04A4C3981C4F2C8600B3B290 /* NSString+Stripe_CardBrands.h in Headers */,
04A488431CA3580700506E53 /* UINavigationController+Stripe_Completion.h in Headers */,
@ -1510,6 +1547,7 @@
049E84E61A605EF0000B66CD /* STPAPIClient.h in Headers */,
04F94DBD1D229F95004FC826 /* UITableViewCell+Stripe_Borders.h in Headers */,
049E84E91A605EF0000B66CD /* STPBankAccount.h in Headers */,
C15993401D88089E0047950D /* STPShippingMethodTableViewCell.h in Headers */,
04B31DD51D08E6E200EF1631 /* STPCustomer.h in Headers */,
04633AFB1CD1299B009D4FB5 /* NSString+Stripe.h in Headers */,
04B31E001D131D9000EF1631 /* STPRememberMePaymentCell.h in Headers */,
@ -1554,6 +1592,7 @@
04F94DC51D22A1FD004FC826 /* STPCheckoutAccountLookup.h in Headers */,
04A4883E1CA3568800506E53 /* STPBlocks.h in Headers */,
045D71211CEFA57000F6CD65 /* UIViewController+Stripe_Promises.h in Headers */,
C159932A1D88084D0047950D /* STPShippingAddressViewController.h in Headers */,
049880FD1CED5A2300EA4FFD /* STPPaymentConfiguration.h in Headers */,
049A3F9B1CC7DBCC00F57DE7 /* STPPaymentContext.h in Headers */,
04F3BB3E1BA89B1200DE235E /* PKPayment+Stripe.h in Headers */,
@ -1587,6 +1626,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
C15993361D8808680047950D /* STPShippingMethodsViewController.h in Headers */,
04BC29A91CD9A83600318357 /* STPCheckoutAPIClient.h in Headers */,
C11810A71CC6EE840022FB55 /* STPBackendAPIAdapter.h in Headers */,
F12C8DC01D63DE9F00ADA0D7 /* STPPaymentContextAmountModel.h in Headers */,
@ -1601,6 +1641,7 @@
04695AD91C77F9EF00E08063 /* STPDelegateProxy.h in Headers */,
0433EB491BD06313003912B4 /* NSDictionary+Stripe.h in Headers */,
C124A1701CCA968B007D42EE /* STPAnalyticsClient.h in Headers */,
C15993281D8808490047950D /* STPShippingAddressViewController.h in Headers */,
049A3F7A1CC18D5300F57DE7 /* UIView+Stripe_FirstResponder.h in Headers */,
04CDB50E1A5F30A700B854EE /* STPCard.h in Headers */,
C1080F491CBECF7B007B2D89 /* STPAddress.h in Headers */,
@ -1621,6 +1662,7 @@
04BC29BD1CDD535700318357 /* STPSwitchTableViewCell.h in Headers */,
04CDB5121A5F30A700B854EE /* STPToken.h in Headers */,
049952CF1BCF13510088C703 /* STPAPIPostRequest.h in Headers */,
C15993381D8808680047950D /* STPShippingMethodTableViewCell.h in Headers */,
049A3FB21CC9FEFC00F57DE7 /* UIToolbar+Stripe_InputAccessory.h in Headers */,
04E39F521CECF7A100AF3B96 /* STPCardTuple.h in Headers */,
04A488331CA34D3000506E53 /* STPEmailAddressValidator.h in Headers */,
@ -1832,6 +1874,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C15993251D8807930047950D /* stp_shipping_form@3x.png in Resources */,
0438EFA81B741C2800D506CC /* stp_card_amex@3x.png in Resources */,
0438EFC61B741C2800D506CC /* stp_card_jcb@3x.png in Resources */,
0438EFC21B741C2800D506CC /* stp_card_jcb.png in Resources */,
@ -1892,6 +1935,7 @@
0438EFAC1B741C2800D506CC /* stp_card_cvc@2x.png in Resources */,
0438EFCC1B741C2800D506CC /* stp_card_mastercard@3x.png in Resources */,
F1510BB91D5A782E000731AD /* stp_card_form_applepay@3x.png in Resources */,
C15993241D8807930047950D /* stp_shipping_form@2x.png in Resources */,
F1510B0B1D5A4C93000731AD /* stp_card_amex_template.png in Resources */,
F1510BB31D5A782E000731AD /* stp_card_form_applepay.png in Resources */,
0438EFCA1B741C2800D506CC /* stp_card_mastercard@2x.png in Resources */,
@ -1900,6 +1944,7 @@
049A3FA01CC8006800F57DE7 /* stp_icon_add@2x.png in Resources */,
042CA1B81B7BD84100AF0DA6 /* stp_card_placeholder_template@3x.png in Resources */,
F1510B211D5A4C93000731AD /* stp_card_mastercard_template@3x.png in Resources */,
C15993231D8807930047950D /* stp_shipping_form.png in Resources */,
0438EFC81B741C2800D506CC /* stp_card_mastercard.png in Resources */,
F1510B151D5A4C93000731AD /* stp_card_applepay_template@3x.png in Resources */,
04E39F661CED3B0100AF3B96 /* stp_icon_chevron_right_small@2x.png in Resources */,
@ -1916,6 +1961,7 @@
C1B630BB1D1D860100A05285 /* stp_card_amex.png in Resources */,
F1510B161D5A4C93000731AD /* stp_card_applepay_template@3x.png in Resources */,
C1B630BC1D1D860100A05285 /* stp_card_amex@2x.png in Resources */,
C15993471D8829C00047950D /* stp_shipping_form@3x.png in Resources */,
F148AC0C1D5E8DF30014FD92 /* stp_icon_add@3x.png in Resources */,
F148AC0D1D5E8DF30014FD92 /* stp_icon_chevron_left.png in Resources */,
C1363BB31D76337900EB82B4 /* stp_icon_checkmark@2x.png in Resources */,
@ -1934,6 +1980,7 @@
C1B630C11D1D860100A05285 /* stp_card_cvc_amex.png in Resources */,
F1510B4F1D5A4CC4000731AD /* stp_card_form_back@2x.png in Resources */,
F1510B5C1D5A4CC4000731AD /* stp_icon_add@3x.png in Resources */,
C15993461D8829C00047950D /* stp_shipping_form@2x.png in Resources */,
C120574C1D676DD400CFBCB8 /* stp_card_diners_template@3x.png in Resources */,
C1B630C21D1D860100A05285 /* stp_card_cvc_amex@2x.png in Resources */,
C120574F1D676DD400CFBCB8 /* stp_card_form_applepay@3x.png in Resources */,
@ -1972,6 +2019,7 @@
C1B630CF1D1D860100A05285 /* stp_card_mastercard@3x.png in Resources */,
F1510B281D5A4C93000731AD /* stp_card_visa_template@3x.png in Resources */,
F1510B221D5A4C93000731AD /* stp_card_mastercard_template@3x.png in Resources */,
C15993451D8829C00047950D /* stp_shipping_form.png in Resources */,
C120574B1D676DD400CFBCB8 /* stp_card_diners_template@2x.png in Resources */,
F1510B591D5A4CC4000731AD /* stp_card_applepay@3x.png in Resources */,
F1510B5B1D5A4CC4000731AD /* stp_icon_add@2x.png in Resources */,
@ -2107,6 +2155,7 @@
04A4C3901C4F25F900B3B290 /* UIViewController+Stripe_ParentViewController.m in Sources */,
04F94DA01D229F0B004FC826 /* STPPostalCodeValidator.m in Sources */,
C124A17F1CCAA0C2007D42EE /* NSMutableURLRequest+Stripe.m in Sources */,
C15993411D8808A10047950D /* STPShippingMethodTableViewCell.m in Sources */,
04F94DAA1D229F36004FC826 /* STPTheme.m in Sources */,
0439B98A1C454F97005A1ED5 /* STPPaymentMethodsViewController.m in Sources */,
04F94DAE1D229F54004FC826 /* STPColorUtils.m in Sources */,
@ -2121,12 +2170,14 @@
0438EF371B7416BB00D506CC /* STPPaymentCardTextField.m in Sources */,
049E84CC1A605DE0000B66CD /* STPAPIClient.m in Sources */,
049E84CD1A605DE0000B66CD /* STPFormEncoder.m in Sources */,
C159933A1D8808880047950D /* STPShippingAddressViewController.m in Sources */,
04F94DA41D229F1C004FC826 /* STPAddressViewModel.m in Sources */,
049880FF1CED5A2300EA4FFD /* STPPaymentConfiguration.m in Sources */,
04F94DB91D229F86004FC826 /* STPApplePayPaymentMethod.m in Sources */,
04B31DE91D09D25F00EF1631 /* STPPaymentMethodsInternalViewController.m in Sources */,
F12C8DC51D63DE9F00ADA0D7 /* STPPaymentContextAmountModel.m in Sources */,
04633B011CD129CB009D4FB5 /* STPPhoneNumberValidator.m in Sources */,
C159933F1D88089B0047950D /* STPShippingMethodsViewController.m in Sources */,
04A488451CA3580700506E53 /* UINavigationController+Stripe_Completion.m in Sources */,
049E84CF1A605DE0000B66CD /* STPBankAccount.m in Sources */,
049E84D01A605DE0000B66CD /* STPCard.m in Sources */,
@ -2195,11 +2246,14 @@
04BC29BE1CDD535700318357 /* STPSwitchTableViewCell.m in Sources */,
04E39F6B1CED48D500AF3B96 /* UIBarButtonItem+Stripe.m in Sources */,
04CDB5181A5F30A700B854EE /* StripeError.m in Sources */,
C15993371D8808680047950D /* STPShippingMethodsViewController.m in Sources */,
C1363BB91D7633D800EB82B4 /* STPPaymentMethodTableViewCell.m in Sources */,
C15993331D8808680047950D /* STPShippingAddressViewController.m in Sources */,
04633B051CD44F1C009D4FB5 /* STPAPIClient+ApplePay.m in Sources */,
F1C7B8D31DBECF2400D9F6F0 /* STPDispatchFunctions.m in Sources */,
04B31DF41D09F0A800EF1631 /* UIViewController+Stripe_NavigationItemProxy.m in Sources */,
04BC29A51CD8697900318357 /* STPTheme.m in Sources */,
C15993391D8808680047950D /* STPShippingMethodTableViewCell.m in Sources */,
049A3F8A1CC73C7100F57DE7 /* STPPaymentContext.m in Sources */,
0426B9731CEAE3EB006AC8DD /* UITableViewCell+Stripe_Borders.m in Sources */,
04BC29B21CD9AAA800318357 /* STPCheckoutAccount.m in Sources */,

View File

@ -93,6 +93,7 @@ typedef NS_ENUM(NSUInteger, STPBillingAddressFields) {
#pragma clang diagnostic pop
- (BOOL)containsRequiredFields:(STPBillingAddressFields)requiredFields;
- (BOOL)containsRequiredPKFields:(PKAddressField)requiredFields;
+ (PKAddressField)applePayAddressFieldsFromBillingAddressFields:(STPBillingAddressFields)billingAddressFields; FAUXPAS_IGNORED_ON_LINE(APIAvailability);

View File

@ -7,6 +7,7 @@
//
#import <Foundation/Foundation.h>
#import <PassKit/PassKit.h>
@class STPToken;
@ -47,3 +48,12 @@ typedef void (^STPErrorBlock)(NSError * __nullable error);
* @param error The error returned from the response, or nil in one occurs. @see StripeError.h for possible values.
*/
typedef void (^STPTokenCompletionBlock)(STPToken * __nullable token, NSError * __nullable error);
/**
* A callback to be run with a validation result and shipping methods for a
* shipping address.
*
* @param shippingValidationError If the shipping address is invalid, an error describing the issue with the address. Will be nil if the address is valid.
* @param shippingMethods The shipping methods available for the address.
*/
typedef void (^STPShippingMethodsCompletionBlock)(NSError * __nullable shippingValidationError, NSArray<PKShippingMethod *>* __nonnull shippingMethods);

View File

@ -11,6 +11,21 @@
#import "STPPaymentMethod.h"
#import "STPTheme.h"
/**
* These values control the labels used in the shipping info collection form.
*/
typedef NS_ENUM(NSUInteger, STPShippingType) {
/**
* Shipping the purchase to the provided address using a third-party
* shipping company.
*/
STPShippingTypeShipping,
/**
* Delivering the purchase by the seller.
*/
STPShippingTypeDelivery,
};
NS_ASSUME_NONNULL_BEGIN
@ -46,6 +61,16 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property(nonatomic)STPBillingAddressFields requiredBillingAddressFields;
/**
* The billing address fields the user must fill out when prompted for their shipping info.
*/
@property(nonatomic)PKAddressField requiredShippingAddressFields;
/**
* The type of shipping for this purchase. This property sets the labels displayed when the user is prompted for shipping info, and whether they should also be asked to select a shipping method. The default value is STPShippingTypeShipping.
*/
@property(nonatomic)STPShippingType shippingType;
/**
* The name of your company, for displaying to the user during payment flows. For example, when using Apple Pay, the payment sheet's final line item will read "PAY {companyName}". This defaults to the name of your iOS application.
*/

View File

@ -0,0 +1,92 @@
//
// STPShippingAddressViewController.h
// Stripe
//
// Created by Ben Guo on 8/29/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <PassKit/PassKit.h>
#import "STPTheme.h"
#import "STPPaymentContext.h"
NS_ASSUME_NONNULL_BEGIN
@protocol STPShippingAddressViewControllerDelegate;
/** This view controller contains a shipping address collection form. It renders a right bar button item that submits the form, so it must be shown inside a `UINavigationController`. Depending on your configuration's shippingType, the view controller may present a shipping method selection form after the user enters an address.
*/
@interface STPShippingAddressViewController : UIViewController
/**
* A convenience initializer; equivalent to calling `initWithConfiguration:[STPPaymentConfiguration sharedConfiguration] theme:[STPTheme defaultTheme] currency:nil shippingAddress:nil selectedShippingMethod:nil`.
*/
- (instancetype)init;
/**
* Initializes a new `STPShippingAddressCardViewController` with the provided parameters.
*
* @param configuration The configuration to use (this determines the required shipping address fields and shipping type). @see STPPaymentConfiguration
* @param theme The theme to use to inform the view controller's visual appearance. @see STPTheme
* @param currency The currency to use when displaying amounts for shipping methods. The default is USD.
* @param shippingAddress If set, the shipping address view controller will be pre-filled with this address. @see STPAddress
* @param selectedShippingMethod If set, the shipping methods view controller will use this method as the selected shipping method. If `selectedShippingMethod` is nil, the first shipping method in the array of methods returned by your delegate will be selected.
*/
- (instancetype)initWithConfiguration:(STPPaymentConfiguration *)configuration
theme:(STPTheme *)theme
currency:(nullable NSString *)currency
shippingAddress:(nullable STPAddress *)shippingAddress
selectedShippingMethod:(nullable PKShippingMethod *)selectedShippingMethod;
/**
* The view controller's delegate. This must be set before showing the view controller in order for it to work properly. @see STPShippingAddressViewControllerDelegate
*/
@property(nonatomic, weak) id<STPShippingAddressViewControllerDelegate> delegate;
/**
* If you're pushing `STPShippingAddressViewController` onto an existing `UINavigationController`'s stack, you should use this method to dismiss it, since it may have pushed an additional shipping method view controller onto the navigation controller's stack.
*
* @param hostViewController the view controller that the shipping address view controller was pushed onto.
*/
- (void)dismissWithHostViewController:(UIViewController *)hostViewController;
@end
/**
* An `STPShippingAddressViewControllerDelegate` is notified when an `STPShippingAddressViewController` receives an address, completes with an address, or is cancelled.
*/
@protocol STPShippingAddressViewControllerDelegate <NSObject>
/**
* Called when the user cancels entering a shipping address. You should dismiss (or pop) the view controller at this point.
*
* @param addressViewController the view controller that has been cancelled
*/
- (void)shippingAddressViewControllerDidCancel:(STPShippingAddressViewController *)addressViewController;
/**
* This is called when the user enters a shipping address and taps next. You should validate the address and determine what shipping methods are available, and call the `completion` block when finished. If an error occurrs, call the `completion` block with the error. Otherwise, call the `completion` block with a nil error and an array of available shipping methods. If you don't need to collect a shipping method, you may pass an empty array.
*
* @param addressViewController the view controller where the address was entered
* @param address the address that was entered. @see STPAddress
* @param completion call this callback when you're done validating the address and determining available shipping methods.
*/
- (void)shippingAddressViewController:(STPShippingAddressViewController *)addressViewController
didEnterAddress:(STPAddress *)address
completion:(STPShippingMethodsCompletionBlock)completion;
/**
* This is called when the user selects a shipping method. If no shipping methods are given, or if the shipping type doesn't require a shipping method, this will be called after the user has a shipping address and your validation has succeeded. After updating your app with the user's shipping info, you should dismiss (or pop) the view controller. Note that if `shippingMethod` is non-nil, there will be an additional shipping methods view controller on the navigation controller's stack.
*
* @param addressViewController the view controller where the address was entered
* @param address the address that was entered. @see STPAddress
* @param shippingMethod the shipping method that was selected.
*/
- (void)shippingAddressViewController:(STPShippingAddressViewController *)addressViewController
didFinishWithAddress:(STPAddress *)address
shippingMethod:(nullable PKShippingMethod *)method;
@end
NS_ASSUME_NONNULL_END

View File

@ -37,3 +37,4 @@
#import "STPPaymentConfiguration.h"
#import "STPAPIResponseDecodable.h"
#import "STPFormEncodable.h"
#import "STPShippingAddressViewController.h"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -9,6 +9,8 @@
#import "STPAddress.h"
#import "STPCardValidator.h"
#import "STPPostalCodeValidator.h"
#import "STPEmailAddressValidator.h"
#import "STPPhoneNumberValidator.h"
@implementation STPAddress
@ -82,6 +84,23 @@
return containsFields;
}
- (BOOL)containsRequiredPKFields:(PKAddressField)requiredFields {
BOOL containsFields = YES;
if (requiredFields & PKAddressFieldName) {
containsFields = containsFields && [self.name length] > 0;
}
if (requiredFields & PKAddressFieldEmail) {
containsFields = containsFields && [STPEmailAddressValidator stringIsValidEmailAddress:self.email];
}
if (requiredFields & PKAddressFieldPhone) {
containsFields = containsFields && [STPPhoneNumberValidator stringIsValidPhoneNumber:self.phone];
}
if (requiredFields & PKAddressFieldPostalAddress) {
containsFields = containsFields && [self hasValidPostalAddress];
}
return containsFields;
}
- (BOOL)hasValidPostalAddress {
return (self.line1.length > 0
&& self.city.length > 0

View File

@ -48,6 +48,7 @@ typedef NS_ENUM(NSInteger, STPAddressFieldType) {
@property(nonatomic, weak, readonly) STPFormTextField *textField;
@property(nonatomic, copy) NSString *contents;
@property(nonatomic)STPTheme *theme;
@property(nonatomic, assign) BOOL lastInList;
- (void)delegateCountryCodeDidChange:(NSString *)countryCode;

View File

@ -22,7 +22,6 @@
@property(nonatomic, weak)id<STPAddressFieldTableViewCellDelegate>delegate;
@property(nonatomic, strong) NSString *ourCountryCode;
@property(nonatomic, assign) STPPostalCodeType postalCodeType;
@property(nonatomic, assign) BOOL lastInList;
@end
@implementation STPAddressFieldTableViewCell
@ -81,10 +80,7 @@
_lastInList = lastInList;
_type = type;
self.textField.text = contents;
if (!lastInList) {
self.textField.returnKeyType = UIReturnKeyNext;
}
NSString *ourCountryCode = nil;
if ([self.delegate respondsToSelector:@selector(addressFieldTableViewCountryCode)]) {
ourCountryCode = self.delegate.addressFieldTableViewCountryCode;
@ -104,8 +100,19 @@
[self updateAppearance];
}
- (void)setLastInList:(BOOL)lastInList {
_lastInList = lastInList;
[self updateTextFieldsAndCaptions];
}
- (void)updateTextFieldsAndCaptions {
self.textField.placeholder = [self placeholderForAddressField:self.type];
if (!self.lastInList) {
self.textField.returnKeyType = UIReturnKeyNext;
}
else {
self.textField.returnKeyType = UIReturnKeyDefault;
}
switch (self.type) {
case STPAddressFieldTypeName:
self.textField.keyboardType = UIKeyboardTypeDefault;
@ -134,6 +141,9 @@
if (!self.lastInList) {
self.textField.inputAccessoryView = self.inputAccessoryToolbar;
}
else {
self.textField.inputAccessoryView = nil;
}
break;
case STPAddressFieldTypeCountry:
self.textField.keyboardType = UIKeyboardTypeDefault;
@ -160,6 +170,9 @@
if (!self.lastInList) {
self.textField.inputAccessoryView = self.inputAccessoryToolbar;
}
else {
self.textField.inputAccessoryView = nil;
}
break;
case STPAddressFieldTypeEmail:
self.textField.autocapitalizationType = UITextAutocapitalizationTypeNone;

View File

@ -29,6 +29,7 @@
@property(nonatomic, readonly)BOOL isValid;
- (instancetype)initWithRequiredBillingFields:(STPBillingAddressFields)requiredBillingAddressFields;
- (instancetype)initWithRequiredShippingFields:(PKAddressField)requiredShippingAddressFields;
- (STPAddressFieldTableViewCell *)cellAtIndex:(NSInteger)index;
@end

View File

@ -11,8 +11,9 @@
#import "STPPostalCodeValidator.h"
@interface STPAddressViewModel()<STPAddressFieldTableViewCellDelegate>
@property(nonatomic)BOOL isBillingAddress;
@property(nonatomic)STPBillingAddressFields requiredBillingAddressFields;
@property(nonatomic)PKAddressField requiredShippingAddressFields;
@property(nonatomic)NSArray<STPAddressFieldTableViewCell *> *addressCells;
@property(nonatomic)BOOL showingPostalCodeCell;
@end
@ -24,9 +25,8 @@
- (instancetype)initWithRequiredBillingFields:(STPBillingAddressFields)requiredBillingAddressFields {
self = [super init];
if (self) {
_isBillingAddress = YES;
_requiredBillingAddressFields = requiredBillingAddressFields;
_addressFieldTableViewCountryCode = [[NSLocale autoupdatingCurrentLocale] objectForKey:NSLocaleCountryCode];
switch (requiredBillingAddressFields) {
case STPBillingAddressFieldsNone:
_addressCells = @[];
@ -48,77 +48,117 @@
];
break;
}
[self updatePostalCodeCellIfNecessary];
[self commonInit];
}
return self;
}
- (instancetype)initWithRequiredShippingFields:(PKAddressField)requiredShippingAddressFields {
self = [super init];
if (self) {
_isBillingAddress = NO;
_requiredShippingAddressFields = requiredShippingAddressFields;
NSMutableArray *cells = [NSMutableArray new];
if (requiredShippingAddressFields & PKAddressFieldName) {
[cells addObject:[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeName contents:@"" lastInList:NO delegate:self]];
}
if (requiredShippingAddressFields & PKAddressFieldEmail) {
[cells addObject:[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeEmail contents:@"" lastInList:NO delegate:self]];
}
if (requiredShippingAddressFields & PKAddressFieldPostalAddress) {
NSMutableArray *postalCells = [@[
[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeName contents:@"" lastInList:NO delegate:self],
[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeLine1 contents:@"" lastInList:NO delegate:self],
[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeLine2 contents:@"" lastInList:NO delegate:self],
[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeCity contents:@"" lastInList:NO delegate:self],
[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeState contents:@"" lastInList:NO delegate:self],
// Postal code cell will be added later if necessary
[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeCountry contents:_addressFieldTableViewCountryCode lastInList:NO delegate:self],
] mutableCopy];
if (requiredShippingAddressFields & PKAddressFieldName) {
[postalCells removeObjectAtIndex:0];
}
[cells addObjectsFromArray:postalCells];
}
if (requiredShippingAddressFields & PKAddressFieldPhone) {
[cells addObject:[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypePhone contents:@"" lastInList:NO delegate:self]];
}
STPAddressFieldTableViewCell *lastCell = [cells lastObject];
if (lastCell != nil) {
lastCell.lastInList = YES;
}
_addressCells = [cells copy];
[self commonInit];
}
return self;
}
- (void)commonInit {
_addressFieldTableViewCountryCode = [[NSLocale autoupdatingCurrentLocale] objectForKey:NSLocaleCountryCode];
[self updatePostalCodeCellIfNecessary];
}
- (void)updatePostalCodeCellIfNecessary {
STPPostalCodeType postalCodeType = [STPPostalCodeValidator postalCodeTypeForCountryCode:_addressFieldTableViewCountryCode];
BOOL shouldBeShowingPostalCode = (postalCodeType != STPCountryPostalCodeTypeNotRequired);
if (shouldBeShowingPostalCode && !self.showingPostalCodeCell) {
switch (self.requiredBillingAddressFields) {
case STPBillingAddressFieldsNone:
// Do nothing
break;
case STPBillingAddressFieldsZip:
self.addressCells = @[
[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeZip contents:@"" lastInList:YES delegate:self]
];
[self.delegate addressViewModel:self addedCellAtIndex:0];
if (self.isBillingAddress && self.requiredBillingAddressFields == STPBillingAddressFieldsZip) {
self.addressCells = @[
[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeZip contents:@"" lastInList:YES delegate:self]
];
[self.delegate addressViewModel:self addedCellAtIndex:0];
[self.delegate addressViewModelDidChange:self];
}
else if (self.containsStateAndPostalFields) {
// Add after STPAddressFieldTypeState
NSUInteger stateFieldIndex = [self.addressCells indexOfObjectPassingTest:^BOOL(STPAddressFieldTableViewCell * _Nonnull obj, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
return (obj.type == STPAddressFieldTypeState);
}];
if (stateFieldIndex != NSNotFound) {
NSUInteger zipFieldIndex = stateFieldIndex + 1;
NSMutableArray<STPAddressFieldTableViewCell *> *mutableAddressCells = self.addressCells.mutableCopy;
[mutableAddressCells insertObject:[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeZip contents:@"" lastInList:NO delegate:self]
atIndex:zipFieldIndex];
self.addressCells = mutableAddressCells.copy;
[self.delegate addressViewModel:self addedCellAtIndex:zipFieldIndex];
[self.delegate addressViewModelDidChange:self];
break;
case STPBillingAddressFieldsFull: {
// Add after STPAddressFieldTypeState
NSUInteger stateFieldIndex = [self.addressCells indexOfObjectPassingTest:^BOOL(STPAddressFieldTableViewCell * _Nonnull obj, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
return (obj.type == STPAddressFieldTypeState);
}];
if (stateFieldIndex != NSNotFound) {
NSUInteger zipFieldIndex = stateFieldIndex + 1;
NSMutableArray<STPAddressFieldTableViewCell *> *mutableAddressCells = self.addressCells.mutableCopy;
[mutableAddressCells insertObject:[[STPAddressFieldTableViewCell alloc] initWithType:STPAddressFieldTypeZip contents:@"" lastInList:NO delegate:self]
atIndex:zipFieldIndex];
self.addressCells = mutableAddressCells.copy;
[self.delegate addressViewModel:self addedCellAtIndex:zipFieldIndex];
[self.delegate addressViewModelDidChange:self];
}
break;
}
}
}
else if (!shouldBeShowingPostalCode && self.showingPostalCodeCell) {
switch (self.requiredBillingAddressFields) {
case STPBillingAddressFieldsNone:
// Do nothing
break;
case STPBillingAddressFieldsZip:
self.addressCells = @[];
[self.delegate addressViewModel:self removedCellAtIndex:0];
if (self.isBillingAddress && self.requiredBillingAddressFields == STPBillingAddressFieldsZip) {
self.addressCells = @[];
[self.delegate addressViewModel:self removedCellAtIndex:0];
[self.delegate addressViewModelDidChange:self];
}
else if (self.containsStateAndPostalFields) {
NSUInteger zipFieldIndex = [self.addressCells indexOfObjectPassingTest:^BOOL(STPAddressFieldTableViewCell * _Nonnull obj, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
return (obj.type == STPAddressFieldTypeZip);
}];
if (zipFieldIndex != NSNotFound) {
NSMutableArray<STPAddressFieldTableViewCell *> *mutableAddressCells = self.addressCells.mutableCopy;
[mutableAddressCells removeObjectAtIndex:zipFieldIndex];
self.addressCells = mutableAddressCells.copy;
[self.delegate addressViewModel:self removedCellAtIndex:zipFieldIndex];
[self.delegate addressViewModelDidChange:self];
break;
case STPBillingAddressFieldsFull: {
NSUInteger zipFieldIndex = [self.addressCells indexOfObjectPassingTest:^BOOL(STPAddressFieldTableViewCell * _Nonnull obj, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
return (obj.type == STPAddressFieldTypeZip);
}];
if (zipFieldIndex != NSNotFound) {
NSMutableArray<STPAddressFieldTableViewCell *> *mutableAddressCells = self.addressCells.mutableCopy;
[mutableAddressCells removeObjectAtIndex:zipFieldIndex];
self.addressCells = mutableAddressCells.copy;
[self.delegate addressViewModel:self removedCellAtIndex:zipFieldIndex];
[self.delegate addressViewModelDidChange:self];
}
break;
}
}
}
self.showingPostalCodeCell = shouldBeShowingPostalCode;
}
- (BOOL)containsStateAndPostalFields {
if (self.isBillingAddress) {
return self.requiredBillingAddressFields == STPBillingAddressFieldsFull;
}
else {
return (self.requiredShippingAddressFields & PKAddressFieldPostalAddress) == PKAddressFieldPostalAddress;
}
}
- (STPAddressFieldTableViewCell *)cellAtIndex:(NSInteger)index {
return self.addressCells[index];
}
@ -140,7 +180,12 @@
}
- (BOOL)isValid {
return [self.address containsRequiredFields:self.requiredBillingAddressFields];
if (self.isBillingAddress) {
return [self.address containsRequiredFields:self.requiredBillingAddressFields];
}
else {
return [self.address containsRequiredPKFields:self.requiredShippingAddressFields];
}
}
- (void)setAddressFieldTableViewCountryCode:(NSString *)addressFieldTableViewCountryCode {

View File

@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (UIImage *)largeCardFrontImage;
+ (UIImage *)largeCardBackImage;
+ (UIImage *)largeCardApplePayImage;
+ (UIImage *)largeShippingImage;
+ (UIImage *)safeImageNamed:(NSString *)imageName
templateIfAvailable:(BOOL)templateIfAvailable;

View File

@ -98,6 +98,10 @@
return [self safeImageNamed:@"stp_card_form_applepay" templateIfAvailable:YES];
}
+ (UIImage *)largeShippingImage {
return [self safeImageNamed:@"stp_shipping_form" templateIfAvailable:YES];
}
+ (UIImage *)safeImageNamed:(NSString *)imageName
templateIfAvailable:(BOOL)templateIfAvailable {
FAUXPAS_IGNORED_IN_METHOD(APIAvailability);

View File

@ -32,8 +32,10 @@
if (self) {
_additionalPaymentMethods = STPPaymentMethodTypeAll;
_requiredBillingAddressFields = STPBillingAddressFieldsNone;
_requiredShippingAddressFields = PKAddressFieldNone;
_companyName = [NSBundle stp_applicationName];
_smsAutofillDisabled = NO;
_shippingType = STPShippingTypeShipping;
}
return self;
}
@ -43,6 +45,8 @@
copy.publishableKey = self.publishableKey;
copy.additionalPaymentMethods = self.additionalPaymentMethods;
copy.requiredBillingAddressFields = self.requiredBillingAddressFields;
copy.requiredShippingAddressFields = self.requiredShippingAddressFields;
copy.shippingType = self.shippingType;
copy.companyName = self.companyName;
copy.appleMerchantIdentifier = self.appleMerchantIdentifier;
copy.smsAutofillDisabled = self.smsAutofillDisabled;

View File

@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface STPPhoneNumberValidator : NSObject
+ (BOOL)stringIsValidPartialPhoneNumber:(NSString *)string;
+ (BOOL)stringIsValidPhoneNumber:(NSString *)string;
+ (BOOL)stringIsValidPhoneNumber:(nullable NSString *)string;
+ (BOOL)stringIsValidPartialPhoneNumber:(NSString *)string
forCountryCode:(nullable NSString *)countryCode;
+ (BOOL)stringIsValidPhoneNumber:(NSString *)string

View File

@ -25,6 +25,9 @@
}
+ (BOOL)stringIsValidPhoneNumber:(NSString *)string {
if (!string) {
return NO;
}
return [self stringIsValidPhoneNumber:string forCountryCode:nil];
}

View File

@ -0,0 +1,349 @@
//
// STPShippingAddressViewController.m
// Stripe
//
// Created by Ben Guo on 8/29/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPShippingAddressViewController.h"
#import "STPTheme.h"
#import "UIBarButtonItem+Stripe.h"
#import "UIViewController+Stripe_NavigationItemProxy.h"
#import "STPAddressViewModel.h"
#import "STPPaymentActivityIndicatorView.h"
#import "STPImageLibrary+Private.h"
#import "STPColorUtils.h"
#import "UIViewController+Stripe_KeyboardAvoiding.h"
#import "UIViewController+Stripe_ParentViewController.h"
#import "NSArray+Stripe_BoundSafe.h"
#import "UITableViewCell+Stripe_Borders.h"
#import "STPAddress.h"
#import "STPLocalizationUtils.h"
#import "STPShippingMethodsViewController.h"
#import "STPPaymentContext+Private.h"
#import "UINavigationController+Stripe_Completion.h"
@interface STPShippingAddressViewController ()<STPAddressViewModelDelegate, UITableViewDelegate, UITableViewDataSource, STPShippingMethodsViewControllerDelegate>
@property(nonatomic)STPPaymentConfiguration *configuration;
@property(nonatomic)NSString *currency;
@property(nonatomic)STPTheme *theme;
@property(nonatomic)PKShippingMethod *selectedShippingMethod;
@property(nonatomic, weak)UITableView *tableView;
@property(nonatomic, weak)UIImageView *imageView;
@property(nonatomic)UIBarButtonItem *nextItem;
@property(nonatomic)UIBarButtonItem *backItem;
@property(nonatomic)UIBarButtonItem *cancelItem;
@property(nonatomic)BOOL loading;
@property(nonatomic)STPPaymentActivityIndicatorView *activityIndicator;
@property(nonatomic)STPAddressViewModel *addressViewModel;
@end
@implementation STPShippingAddressViewController
- (instancetype)init {
return [self initWithConfiguration:[STPPaymentConfiguration sharedConfiguration] theme:[STPTheme defaultTheme] currency:nil shippingAddress:nil selectedShippingMethod:nil];
}
- (instancetype)initWithConfiguration:(STPPaymentConfiguration *)configuration
theme:(STPTheme *)theme
currency:(NSString *)currency
shippingAddress:(STPAddress *)shippingAddress
selectedShippingMethod:(PKShippingMethod *)selectedShippingMethod {
self = [super initWithNibName:nil bundle:nil];
if (self) {
_configuration = configuration;
_currency = currency ?: @"usd";
_theme = theme;
_selectedShippingMethod = selectedShippingMethod;
_addressViewModel = [[STPAddressViewModel alloc] initWithRequiredShippingFields:configuration.requiredShippingAddressFields];
_addressViewModel.delegate = self;
_addressViewModel.address = shippingAddress;
self.title = [self titleForShippingType:self.configuration.shippingType];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.automaticallyAdjustsScrollViewInsets = NO;
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
tableView.sectionHeaderHeight = 30;
[self.view addSubview:tableView];
self.tableView = tableView;
self.backItem = [UIBarButtonItem stp_backButtonItemWithTitle:STPLocalizedString(@"Back", @"Text for back button")
style:UIBarButtonItemStylePlain
target:self
action:@selector(cancel:)];
self.cancelItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self
action:@selector(cancel:)];
UIBarButtonItem *nextItem;
switch (self.configuration.shippingType) {
case STPShippingTypeShipping:
nextItem = [[UIBarButtonItem alloc] initWithTitle:STPLocalizedString(@"Next", @"Text for next button")
style:UIBarButtonItemStyleDone
target:self
action:@selector(next:)];
break;
case STPShippingTypeDelivery:
nextItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
target:self
action:@selector(next:)];
break;
}
self.nextItem = nextItem;
self.stp_navigationItemProxy.rightBarButtonItem = nextItem;
self.stp_navigationItemProxy.rightBarButtonItem.enabled = NO;
UIImageView *imageView = [[UIImageView alloc] initWithImage:[STPImageLibrary largeShippingImage]];
imageView.contentMode = UIViewContentModeCenter;
imageView.frame = CGRectMake(0, 0, self.view.bounds.size.width, imageView.bounds.size.height + (57 * 2));
self.imageView = imageView;
self.tableView.tableHeaderView = imageView;
self.activityIndicator = [[STPPaymentActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 20.0f, 20.0f)];
tableView.dataSource = self;
tableView.delegate = self;
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(endEditing)]];
[self updateAppearance];
[self updateDoneButton];
}
- (void)endEditing {
[self.view endEditing:NO];
}
- (void)updateAppearance {
self.view.backgroundColor = self.theme.primaryBackgroundColor;
[self.nextItem stp_setTheme:self.theme];
[self.cancelItem stp_setTheme:self.theme];
[self.backItem stp_setTheme:self.theme];
self.tableView.allowsSelection = NO;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.backgroundColor = self.theme.primaryBackgroundColor;
if ([STPColorUtils colorIsBright:self.theme.primaryBackgroundColor]) {
self.tableView.indicatorStyle = UIScrollViewIndicatorStyleBlack;
} else {
self.tableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
}
self.imageView.tintColor = self.theme.accentColor;
self.activityIndicator.tintColor = self.theme.accentColor;
for (STPAddressFieldTableViewCell *cell in self.addressViewModel.addressCells) {
cell.theme = self.theme;
}
[self setNeedsStatusBarAppearanceUpdate];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.stp_navigationItemProxy.leftBarButtonItem = [self stp_isAtRootOfNavigationController] ? self.cancelItem : self.backItem;
[self.tableView reloadData];
if (self.navigationController.navigationBar.translucent) {
CGFloat insetTop = CGRectGetMaxY(self.navigationController.navigationBar.frame);
self.tableView.contentInset = UIEdgeInsetsMake(insetTop, 0, 0, 0);
self.tableView.scrollIndicatorInsets = self.tableView.contentInset;
} else {
self.tableView.contentInset = UIEdgeInsetsZero;
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
}
CGPoint offset = self.tableView.contentOffset;
offset.y = -self.tableView.contentInset.top;
self.tableView.contentOffset = offset;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self stp_beginObservingKeyboardAndInsettingScrollView:self.tableView
onChangeBlock:nil];
[[self firstEmptyField] becomeFirstResponder];
}
- (UIResponder *)firstEmptyField {
for (STPAddressFieldTableViewCell *cell in self.addressViewModel.addressCells) {
if (cell.contents.length == 0) {
return cell;
}
}
return nil;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.tableView.frame = self.view.bounds;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.view endEditing:YES];
}
- (void)setLoading:(BOOL)loading {
if (loading == _loading) {
return;
}
_loading = loading;
[self.stp_navigationItemProxy setHidesBackButton:loading animated:YES];
self.stp_navigationItemProxy.leftBarButtonItem.enabled = !loading;
self.activityIndicator.animating = loading;
if (loading) {
[self.tableView endEditing:YES];
UIBarButtonItem *loadingItem = [[UIBarButtonItem alloc] initWithCustomView:self.activityIndicator];
[self.stp_navigationItemProxy setRightBarButtonItem:loadingItem animated:YES];
} else {
[self.stp_navigationItemProxy setRightBarButtonItem:self.nextItem animated:YES];
}
for (UITableViewCell *cell in self.addressViewModel.addressCells) {
cell.userInteractionEnabled = !loading;
[UIView animateWithDuration:0.1f animations:^{
cell.alpha = loading ? 0.7f : 1.0f;
}];
}
}
- (void)cancel:(__unused id)sender {
[self.delegate shippingAddressViewControllerDidCancel:self];
}
- (void)next:(__unused id)sender {
STPAddress *address = self.addressViewModel.address;
switch (self.configuration.shippingType) {
case STPShippingTypeShipping: {
self.loading = YES;
[self.delegate shippingAddressViewController:self didEnterAddress:address completion:^(NSError *shippingValidationError, NSArray<PKShippingMethod *> * _Nonnull shippingMethods) {
self.loading = NO;
if (shippingValidationError == nil) {
if ([shippingMethods count] > 0) {
STPShippingMethodsViewController *nextViewController = [[STPShippingMethodsViewController alloc] initWithShippingMethods:shippingMethods
selectedShippingMethod:self.selectedShippingMethod
currency:self.currency
theme:self.theme];
nextViewController.delegate = self;
[self.navigationController pushViewController:nextViewController animated:YES];
}
else {
[self.delegate shippingAddressViewController:self
didFinishWithAddress:address
shippingMethod:nil];
}
}
else {
[self handleShippingValidationError:shippingValidationError];
}
}];
break;
}
case STPShippingTypeDelivery:
[self.delegate shippingAddressViewController:self
didFinishWithAddress:address
shippingMethod:nil];
break;
}
}
- (void)updateDoneButton {
self.stp_navigationItemProxy.rightBarButtonItem.enabled = self.addressViewModel.isValid;
}
- (void)handleShippingValidationError:(NSError *)error {
[[self firstEmptyField] becomeFirstResponder];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:error.localizedDescription
message:error.localizedFailureReason
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:STPLocalizedString(@"OK", @"ok button")
style:UIAlertActionStyleCancel
handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)dismissWithHostViewController:(UIViewController *)hostViewController {
[self.navigationController stp_popToViewController:hostViewController animated:YES completion:nil];
}
#pragma mark - STPAddressViewModelDelegate
- (void)addressViewModel:(__unused STPAddressViewModel *)addressViewModel addedCellAtIndex:(NSUInteger)index {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (void)addressViewModel:(__unused STPAddressViewModel *)addressViewModel removedCellAtIndex:(NSUInteger)index {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (void)addressViewModelDidChange:(__unused STPAddressViewModel *)addressViewModel {
[self updateDoneButton];
}
#pragma mark - UITableView
- (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(__unused NSInteger)section {
return self.addressViewModel.addressCells.count;
}
- (UITableViewCell *)tableView:(__unused UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.addressViewModel.addressCells stp_boundSafeObjectAtIndex:indexPath.row];
cell.backgroundColor = [UIColor clearColor];
cell.contentView.backgroundColor = self.theme.secondaryBackgroundColor;
return cell;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
BOOL topRow = (indexPath.row == 0);
BOOL bottomRow = ([self tableView:tableView numberOfRowsInSection:indexPath.section] - 1 == indexPath.row);
[cell stp_setBorderColor:self.theme.tertiaryBackgroundColor];
[cell stp_setTopBorderHidden:!topRow];
[cell stp_setBottomBorderHidden:!bottomRow];
[cell stp_setFakeSeparatorColor:self.theme.quaternaryBackgroundColor];
[cell stp_setFakeSeparatorLeftInset:15.0f];
}
- (CGFloat)tableView:(__unused UITableView *)tableView heightForFooterInSection:(__unused NSInteger)section {
return 0.01f;
}
- (UIView *)tableView:(__unused UITableView *)tableView viewForFooterInSection:(__unused NSInteger)section {
return [UIView new];
}
- (CGFloat)tableView:(__unused UITableView *)tableView heightForHeaderInSection:(__unused NSInteger)section {
return 0.01f;
}
- (UIView *)tableView:(__unused UITableView *)tableView viewForHeaderInSection:(__unused NSInteger)section {
return [UIView new];
}
- (NSString *)titleForShippingType:(STPShippingType)type {
if (self.configuration.requiredShippingAddressFields & PKAddressFieldPostalAddress) {
switch (type) {
case STPShippingTypeShipping:
return STPLocalizedString(@"Shipping", @"Title for shipping info form");
break;
case STPShippingTypeDelivery:
return STPLocalizedString(@"Delivery", @"Title for delivery info form");
break;
}
}
else {
return STPLocalizedString(@"Contact", @"Title for contact info form");
}
}
#pragma mark - STPShippingMethodsViewControllerDelegate
- (void)shippingMethodsViewController:(__unused STPShippingMethodsViewController *)methodsViewController
didFinishWithShippingMethod:(PKShippingMethod *)method {
[self.delegate shippingAddressViewController:self
didFinishWithAddress:self.addressViewModel.address
shippingMethod:method];
}
@end

View File

@ -0,0 +1,20 @@
//
// STPShippingMethodTableViewCell.h
// Stripe
//
// Created by Ben Guo on 8/30/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <PassKit/PassKit.h>
#import "STPTheme.h"
NS_ASSUME_NONNULL_BEGIN
@interface STPShippingMethodTableViewCell : UITableViewCell
@property(nonatomic)STPTheme *theme;
- (void)setShippingMethod:(PKShippingMethod *)method currency:(NSString *)currency;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,108 @@
//
// STPShippingMethodTableViewCell.m
// Stripe
//
// Created by Ben Guo on 8/30/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPShippingMethodTableViewCell.h"
#import "STPImageLibrary+Private.h"
#import "STPLocalizationUtils.h"
#import "NSDecimalNumber+Stripe_Currency.h"
@interface STPShippingMethodTableViewCell ()
@property(nonatomic, weak) UILabel *titleLabel;
@property(nonatomic, weak) UILabel *subtitleLabel;
@property(nonatomic, weak) UILabel *amountLabel;
@property(nonatomic, weak) UIImageView *checkmarkIcon;
@property(nonatomic)PKShippingMethod *shippingMethod;
@property(nonatomic) NSNumberFormatter *numberFormatter;
@end
@implementation STPShippingMethodTableViewCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
_theme = [STPTheme new];
UILabel *titleLabel = [UILabel new];
_titleLabel = titleLabel;
UILabel *subtitleLabel = [UILabel new];
_subtitleLabel = subtitleLabel;
UILabel *amountLabel = [UILabel new];
_amountLabel = amountLabel;
UIImageView *checkmarkIcon = [[UIImageView alloc] initWithImage:[STPImageLibrary checkmarkIcon]];
_checkmarkIcon = checkmarkIcon;
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterCurrencyStyle;
formatter.usesGroupingSeparator = YES;
_numberFormatter = formatter;
[self.contentView addSubview:titleLabel];
[self.contentView addSubview:subtitleLabel];
[self.contentView addSubview:amountLabel];
[self.contentView addSubview:checkmarkIcon];
[self updateAppearance];
}
return self;
}
- (void)setTheme:(STPTheme *)theme {
_theme = theme;
[self updateAppearance];
}
- (void)setShippingMethod:(PKShippingMethod *)method currency:(NSString *)currency {
_shippingMethod = method;
self.titleLabel.text = method.label;
self.subtitleLabel.text = method.detail;
NSMutableDictionary<NSString *,NSString *>*localeInfo = [@{NSLocaleCurrencyCode: currency} mutableCopy];
localeInfo[NSLocaleLanguageCode] = [[NSLocale preferredLanguages] firstObject];
NSString *localeID = [NSLocale localeIdentifierFromComponents:localeInfo];
NSLocale *locale = [NSLocale localeWithLocaleIdentifier:localeID];
self.numberFormatter.locale = locale;
NSInteger amount = [method.amount stp_amountWithCurrency:currency];
if (amount == 0) {
self.amountLabel.text = STPLocalizedString(@"Free", @"Label for free shipping method");
}
else {
NSDecimalNumber *number = [NSDecimalNumber stp_decimalNumberWithAmount:amount
currency:currency];
self.amountLabel.text = [self.numberFormatter stringFromNumber:number];
}
[self setNeedsLayout];
}
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
[self updateAppearance];
}
- (void)updateAppearance {
self.contentView.backgroundColor = self.theme.secondaryBackgroundColor;
self.backgroundColor = [UIColor clearColor];
self.titleLabel.font = self.theme.font;
self.subtitleLabel.font = self.theme.smallFont;
self.amountLabel.font = self.theme.font;
self.titleLabel.textColor = self.selected ? self.theme.accentColor : self.theme.primaryForegroundColor;
self.amountLabel.textColor = self.titleLabel.textColor;
self.subtitleLabel.textColor = self.selected ? [self.theme.accentColor colorWithAlphaComponent:0.6f] : self.theme.secondaryForegroundColor;
self.checkmarkIcon.tintColor = self.theme.accentColor;
self.checkmarkIcon.hidden = !self.selected;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat midY = CGRectGetMidY(self.bounds);
self.checkmarkIcon.frame = CGRectMake(0, 0, 14, 14);
self.checkmarkIcon.center = CGPointMake(CGRectGetWidth(self.bounds) - 15 - CGRectGetMidX(self.checkmarkIcon.bounds), midY);
[self.amountLabel sizeToFit];
self.amountLabel.center = CGPointMake(CGRectGetMinX(self.checkmarkIcon.frame) - 15 - CGRectGetMidX(self.amountLabel.bounds), midY);
CGFloat labelWidth = CGRectGetMinX(self.amountLabel.frame) - 30;
[self.titleLabel sizeToFit];
self.titleLabel.frame = CGRectMake(15, 8, labelWidth, self.titleLabel.frame.size.height);
[self.subtitleLabel sizeToFit];
self.subtitleLabel.frame = CGRectMake(15, self.bounds.size.height - 8 - self.subtitleLabel.frame.size.height, labelWidth, self.subtitleLabel.frame.size.height);
}
@end

View File

@ -0,0 +1,35 @@
//
// STPShippingMethodsViewController.h
// Stripe
//
// Created by Ben Guo on 8/29/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <PassKit/PassKit.h>
#import "STPTheme.h"
NS_ASSUME_NONNULL_BEGIN
@protocol STPShippingMethodsViewControllerDelegate;
@interface STPShippingMethodsViewController : UIViewController
- (instancetype)initWithShippingMethods:(NSArray<PKShippingMethod *>*)methods
selectedShippingMethod:(nullable PKShippingMethod *)selectedMethod
currency:(NSString *)currency
theme:(STPTheme *)theme;
@property(nonatomic, weak) id<STPShippingMethodsViewControllerDelegate> delegate;
@end
@protocol STPShippingMethodsViewControllerDelegate <NSObject>
- (void)shippingMethodsViewController:(STPShippingMethodsViewController *)methodsViewController
didFinishWithShippingMethod:(PKShippingMethod *)method;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,185 @@
//
// STPShippingMethodsViewController.m
// Stripe
//
// Created by Ben Guo on 8/29/16.
// Copyright © 2016 Stripe, Inc. All rights reserved.
//
#import "STPShippingMethodsViewController.h"
#import "STPLocalizationUtils.h"
#import "UIBarButtonItem+Stripe.h"
#import "UIViewController+Stripe_NavigationItemProxy.h"
#import "STPImageLibrary+Private.h"
#import "STPColorUtils.h"
#import "UITableViewCell+Stripe_Borders.h"
#import "NSArray+Stripe_BoundSafe.h"
#import "STPShippingMethodTableViewCell.h"
static NSString *const STPShippingMethodCellReuseIdentifier = @"STPShippingMethodCellReuseIdentifier";
@interface STPShippingMethodsViewController () <UITableViewDataSource, UITableViewDelegate>
@property(nonatomic)NSArray<PKShippingMethod *>*shippingMethods;
@property(nonatomic)PKShippingMethod *selectedShippingMethod;
@property(nonatomic)STPTheme *theme;
@property(nonatomic)NSString *currency;
@property(nonatomic, weak)UITableView *tableView;
@property(nonatomic, weak)UIImageView *imageView;
@property(nonatomic)UIBarButtonItem *doneItem;
@property(nonatomic)UIBarButtonItem *backItem;
@end
@implementation STPShippingMethodsViewController
- (instancetype)initWithShippingMethods:(NSArray<PKShippingMethod *>*)methods
selectedShippingMethod:(PKShippingMethod *)selectedMethod
currency:(NSString *)currency
theme:(STPTheme *)theme {
self = [super initWithNibName:nil bundle:nil];
if (self) {
_shippingMethods = methods;
if (selectedMethod != nil && [methods indexOfObject:selectedMethod] != NSNotFound) {
_selectedShippingMethod = selectedMethod;
}
else {
_selectedShippingMethod = [methods stp_boundSafeObjectAtIndex:0];
}
_theme = theme;
_currency = currency;
self.title = STPLocalizedString(@"Shipping", @"Title for shipping info form");
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.automaticallyAdjustsScrollViewInsets = NO;
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
[tableView registerClass:[STPShippingMethodTableViewCell class] forCellReuseIdentifier:STPShippingMethodCellReuseIdentifier];
tableView.sectionHeaderHeight = 30;
[self.view addSubview:tableView];
self.tableView = tableView;
UIBarButtonItem *doneItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(done:)];
self.doneItem = doneItem;
self.backItem = [UIBarButtonItem stp_backButtonItemWithTitle:STPLocalizedString(@"Back", @"Text for back button") style:UIBarButtonItemStylePlain target:self action:@selector(cancel:)];
self.stp_navigationItemProxy.rightBarButtonItem = doneItem;
UIImageView *imageView = [[UIImageView alloc] initWithImage:[STPImageLibrary largeShippingImage]];
imageView.contentMode = UIViewContentModeCenter;
imageView.frame = CGRectMake(0, 0, self.view.bounds.size.width, imageView.bounds.size.height + (57 * 2));
self.imageView = imageView;
self.tableView.tableHeaderView = imageView;
tableView.dataSource = self;
tableView.delegate = self;
[self updateAppearance];
}
- (void)updateAppearance {
self.view.backgroundColor = self.theme.primaryBackgroundColor;
[self.doneItem stp_setTheme:self.theme];
self.tableView.allowsSelection = YES;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.tableView.backgroundColor = self.theme.primaryBackgroundColor;
if ([STPColorUtils colorIsBright:self.theme.primaryBackgroundColor]) {
self.tableView.indicatorStyle = UIScrollViewIndicatorStyleBlack;
} else {
self.tableView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
}
self.imageView.tintColor = self.theme.accentColor;
for (UITableViewCell *cell in [self.tableView visibleCells]) {
STPShippingMethodTableViewCell *shippingCell = (STPShippingMethodTableViewCell *)cell;
[shippingCell setTheme:self.theme];
}
[self setNeedsStatusBarAppearanceUpdate];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.tableView reloadData];
self.stp_navigationItemProxy.leftBarButtonItem = self.backItem;
if (self.navigationController.navigationBar.translucent) {
CGFloat insetTop = CGRectGetMaxY(self.navigationController.navigationBar.frame);
self.tableView.contentInset = UIEdgeInsetsMake(insetTop, 0, 0, 0);
self.tableView.scrollIndicatorInsets = self.tableView.contentInset;
} else {
self.tableView.contentInset = UIEdgeInsetsZero;
self.tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
}
CGPoint offset = self.tableView.contentOffset;
offset.y = -self.tableView.contentInset.top;
self.tableView.contentOffset = offset;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.tableView.frame = self.view.bounds;
}
- (void)cancel:(__unused id)sender {
[self.navigationController popViewControllerAnimated:YES];
}
- (void)done:(__unused id)sender {
[self.delegate shippingMethodsViewController:self didFinishWithShippingMethod:self.selectedShippingMethod];
}
#pragma mark - UITableView
- (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(__unused NSInteger)section {
return self.shippingMethods.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
STPShippingMethodTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:STPShippingMethodCellReuseIdentifier forIndexPath:indexPath];
PKShippingMethod *method = [self.shippingMethods stp_boundSafeObjectAtIndex:indexPath.row];
cell.theme = self.theme;
[cell setShippingMethod:method currency:self.currency];
cell.selected = [method.identifier isEqualToString:self.selectedShippingMethod.identifier];
return cell;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
BOOL topRow = (indexPath.row == 0);
BOOL bottomRow = ([self tableView:tableView numberOfRowsInSection:indexPath.section] - 1 == indexPath.row);
[cell stp_setBorderColor:self.theme.tertiaryBackgroundColor];
[cell stp_setTopBorderHidden:!topRow];
[cell stp_setBottomBorderHidden:!bottomRow];
[cell stp_setFakeSeparatorColor:self.theme.quaternaryBackgroundColor];
[cell stp_setFakeSeparatorLeftInset:15.0f];
}
- (CGFloat)tableView:(__unused UITableView *)tableView heightForRowAtIndexPath:(__unused NSIndexPath *)indexPath {
return 57;
}
- (CGFloat)tableView:(__unused UITableView *)tableView heightForFooterInSection:(__unused NSInteger)section {
return 27.0f;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(__unused NSInteger)section {
return tableView.sectionHeaderHeight;
}
- (UIView *)tableView:(__unused UITableView *)tableView viewForHeaderInSection:(__unused NSInteger)section {
UILabel *label = [UILabel new];
label.font = self.theme.smallFont;
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
style.firstLineHeadIndent = 15;
NSDictionary *attributes = @{NSParagraphStyleAttributeName: style};
label.textColor = self.theme.secondaryForegroundColor;
label.attributedText = [[NSAttributedString alloc] initWithString:STPLocalizedString(@"Shipping Method", @"Label for shipping method form") attributes:attributes];
return label;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
self.selectedShippingMethod = [self.shippingMethods stp_boundSafeObjectAtIndex:indexPath.row];
[tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section]
withRowAnimation:UITableViewRowAnimationFade];
}
@end

View File

@ -137,4 +137,29 @@
XCTAssertTrue([address containsRequiredFields:STPBillingAddressFieldsFull]);
}
- (void)testContainsRequiredPKFields {
STPAddress *address = [STPAddress new];
XCTAssertTrue([address containsRequiredPKFields:PKAddressFieldNone]);
XCTAssertFalse([address containsRequiredPKFields:PKAddressFieldAll]);
address.name = @"John Smith";
XCTAssertTrue([address containsRequiredPKFields:PKAddressFieldName]);
XCTAssertFalse([address containsRequiredPKFields:PKAddressFieldEmail]);
address.email = @"john@example.com";
XCTAssertTrue([address containsRequiredPKFields:PKAddressFieldEmail|PKAddressFieldName]);
XCTAssertFalse([address containsRequiredPKFields:PKAddressFieldAll]);
address.phone = @"5555555555";
XCTAssertTrue([address containsRequiredPKFields:PKAddressFieldEmail|PKAddressFieldName|PKAddressFieldPhone]);
XCTAssertFalse([address containsRequiredPKFields:PKAddressFieldAll]);
address.country = @"US";
address.line1 = @"55 John St";
address.city = @"New York";
address.state = @"NY";
address.postalCode = @"12345";
XCTAssertTrue([address containsRequiredPKFields:PKAddressFieldAll]);
}
@end