Reputation: 311
I am trying to set up payment with Braintree, but Braintree does not yet support SwiftUI so I have to integrate it with UIKit. I created a wrapper using UIViewControllerRepresentable
and I am presenting it as a modal using the sheet
function; however, it does not work as expected, it seems it is opening two modals.
The screen when I open the modal:
Here's my wrapper:
import SwiftUI
import BraintreeDropIn
struct BTDropInRepresentable: UIViewControllerRepresentable {
var authorization: String
var handler: BTDropInControllerHandler
init(authorization: String, handler: @escaping BTDropInControllerHandler) {
self.authorization = authorization
self.handler = handler
}
func makeUIViewController(context: Context) -> BTDropInController {
let bTDropInController = BTDropInController(authorization: authorization, request: BTDropInRequest(), handler: handler)!
return bTDropInController
}
func updateUIViewController(_ uiViewController: BTDropInController, context: UIViewControllerRepresentableContext<BTDropInRepresentable>) {
}
}
Here is where I am trying to open the modal:
Button(action: {
self.checkout = true
}) {
HStack {
Spacer()
Text("Checkout")
.fontWeight(.bold)
.font(.body)
Spacer()
}
.padding(.vertical, 12)
.foregroundColor(.white)
.background(Color.blue)
}.sheet(isPresented: self.$checkout) {
BTDropInRepresentable(authorization: self.token!, handler: { (controller, result, error) in
if (error != nil) {
print("ERROR")
} else if (result?.isCancelled == true) {
print("CANCELLED")
} else if result != nil {
print("SUCCESS")
// Use the BTDropInResult properties to update your UI
// result.paymentOptionType
// result.paymentMethod
// result.paymentIcon
// result.paymentDescription
}
controller.dismiss(animated: true, completion: nil)
})
}
Does anyone have experience with Braintree in SwiftUI or with a similar situation? Am I doing something wrong or forgetting something? I know writing my own views for Braintree checkout is an option, but I'd like to avoid that.
Thanks!
Upvotes: 1
Views: 839
Reputation: 241
// swiftUI
import SwiftUI
import BraintreeCard
import BraintreeDropIn
import BraintreeApplePay
// View
struct ContentView: View {
@StateObject var vm = ViewModel()
@State var methodnoun = ""
@State var viewController = UIViewControllerRep()
var body: some View {
VStack {
Spacer()
// Create ClientToken you have to create your server
Button("create Client") {
//make API call nage hit the server and ClientToken
vm.fetchClientToken()
}
.frame(width: 200, height: 40)
.background(.blue)
.foregroundColor(.white)
Spacer()
Button("Open the Braintree PaymentSheet") {
viewController.showDropIn(clientTokenOrTokenizationKey: vm.clientTolken1)
} .frame(width: 300, height: 40)
.background(.blue)
.foregroundColor(.white)
Spacer()
Spacer()
//loadViewController webView
viewController
Spacer()
}
}
}
// View Model
class ViewModel: ObservableObject {
@Published var clientTolken1: String = ""
@Published var title: String = ""
@Published var messageBody: String = ""
@Published var showSuccessAlert: Bool = false
func fetchClientToken() {
// TODO: Switch this URL to your own authenticated API
let clientTokenURL = NSURL(string: "http://localhost:3000/client_token")!
let clientTokenRequest = NSMutableURLRequest(url: clientTokenURL as URL)
clientTokenRequest.setValue("text/plain", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: clientTokenRequest as URLRequest) { (data, response, error) -> Void in
// TODO: Handle errors
let clientToken = String(data: data!, encoding: String.Encoding.utf8)
self.clientTolken1 = clientToken ?? ""
// self.postNonceToServer(paymentMethodNonce: self.clientTolken1)
// print(clientToken)
// As an example, you may wish to present Drop-in at this point.
// Continue to the next section to learn more...
}.resume()
}
[View Page ][1]
func postNonceToServer(paymentMethodNonce: String) {
// Update URL with your server
do {
let paymentURL = URL(string: "http://localhost:3000/checkout")!
var request = URLRequest(url: paymentURL)
request.httpBody = "payment_method_nonce=\(paymentMethodNonce)".data(using: String.Encoding.utf8)
request.httpMethod = "POST"
URLSession.shared.dataTask(with: request) { (data, response, error) -> Void in
// TODO: Handle success or failure
guard let response = response as? HTTPURLResponse else {
return
}
print("Error Code : \(response.statusCode)")
// let clientToken = String(data: data!, encoding: String.Encoding.utf8)
let json = try? JSONSerialization.jsonObject(with: data ?? Data(), options: [])
print(json)
print("---------------")
let dict = json as? [String: Any]
print(dict?["transaction"])
if (error != nil) {
print("Error : \(error)")
}
print(error?.localizedDescription)
}.resume()
} catch {
print(error.localizedDescription)
}
}
}
// create UIViewControllerRepresentable for integrating UIViewController and Swift
struct UIViewControllerRep: UIViewControllerRepresentable {
let viewController = VmController()
func makeUIViewController(context: Context) -> some VmController {
return viewController
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
func showDropIn(clientTokenOrTokenizationKey: String) {
viewController.showDropIn(clientTokenOrTokenizationKey: clientTokenOrTokenizationKey)
}
}
// Create ViewController
class VmController: UIViewController {
func showDropIn(clientTokenOrTokenizationKey: String) {
let request = BTDropInRequest()
print(clientTokenOrTokenizationKey)
let dropIn = BTDropInController(authorization: clientTokenOrTokenizationKey, request: request)
{ (controller, result, error) in
if (error != nil) {
print("ERROR")
} else if (result?.isCanceled == true) {
print("CANCELED")
} else if let result = result {
print("Result paymentMethodType : \(result.paymentMethodType)")
print("Result paymentMethod nonce: \(String(describing: result.paymentMethod?.nonce))")
print("Result paymentIcon : \(result.paymentIcon)")
print("Result Description : \(result.paymentDescription)")
}
controller.dismiss(animated: true, completion: nil)
} ?? BTDropInController()
self.present(dropIn, animated: true, completion: nil)
}
}
Braintree WebView get Card details
refer the official link and setup your client-side and server-side setup. https://developer.paypal.com/braintree/docs/start/hello-client/
Upvotes: 0
Reputation: 27224
Have a look at the error you're receiving and I bet it relates to a custom URL scheme that you'll need to add:
Register a URL type
https://developers.braintreepayments.com/guides/paypal/client-side/ios/v4
Also you need to setup your Drop-in payment methods as well, which are all detailed in that guide I linked.
Upvotes: 1