HI Dima, thanks for your reply and assistance!
The actual payload going over the wire is difficult for me to fetch since it’s being sent by the shopify API, but here’s the swift code makign the api call.
The proof-of-concept app is a series of view controllers (browse catalog, add to cart, apply shipping method, submit payment). This is the last one–payment.
Yes, I realize the token returned by the vault may be invalid, but if it was I’d expect an error more along the lines of “payment declined” instead of “access denied” from the checkoutCompleteWithCreditCard method (?)
I’m happy to zip up the entire project and send if it would be helpful…this is a simple proof-of-concept against development store and test merchant account..so no confidentiality issues at all.
============ PaymentTableViewController.swift ==============
import UIKit
import MobileBuySDK
class PaymentTableViewController: UITableViewController {
@IBOutlet weak var firstName: UITextField!
@IBOutlet weak var lastName: UITextField!
@IBOutlet weak var creditCardNumber: UITextField!
@IBOutlet weak var expiresMonth: UITextField!
@IBOutlet weak var expiresYear: UITextField!
@IBOutlet weak var verificationCode: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
firstName.text = “John”
lastName.text = “Smith”
creditCardNumber.text = “4242424242424242”
expiresMonth.text = “12”
expiresYear.text = “2019”
verificationCode.text = “555”
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) → Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) → Int {
return 6
}
func fetchPaymentToken() {
guard let cardClient = ShoppingData.shopifyCard else {
displayError(“nil card client in (#function)”)
return
}
guard let cardVaultUrl = ShoppingData.cardVaultUrl else {
displayError(“nil cardVaultUrl in (#function)”)
return
}
let creditCard = Card.CreditCard(
firstName: firstName.text ?? “”,
middleName: “A”,
lastName: lastName.text ?? “”,
number: creditCardNumber.text ?? “”,
expiryMonth: expiresMonth.text ?? “”,
expiryYear: expiresYear.text ?? “”,
verificationCode: verificationCode.text ?? “”
)
let task = cardClient.vault(creditCard, to: cardVaultUrl) { token, error in
if let token = token {
print(“Token received is: (token)”)
self.processOrder(paymentToken: token)
} else {
self.displayError(error?.localizedDescription ?? “Unknown error obtaining token”)
}
}
task.resume()
}
@IBAction func submitOrderTapped(_ sender: UIBarButtonItem) {
fetchPaymentToken()
}
func processOrder(paymentToken: String) {
guard let checkoutId = ShoppingData.checkout?.id else {
displayError(“nil checkoutId in (#function)”)
return
}
guard let client = ShoppingData.shopifyClient else {
displayError(“Invalid client in (#function)”)
return
}
guard let billToAddress = ShoppingData.billToAddress else {
displayError(“nil billToAddress in (#function)”)
return
}
guard let paymentDue = ShoppingData.checkout?.paymentDue else {
displayError(“nil paymentDue in (#function)”)
return
}
let idempotencyKey = UUID().uuidString
let payment = Storefront.CreditCardPaymentInput.create(
amount: paymentDue,
idempotencyKey: idempotencyKey,
billingAddress: billToAddress,
vaultId: paymentToken
)
let mutation = Storefront.buildMutation { $0
.checkoutCompleteWithCreditCard(checkoutId: checkoutId, payment: payment) { $0
.payment { $0
.id()
.ready()
}
.checkout { $0
.id()
.ready()
}
.userErrors { $0
.field()
.message()
}
}
}
let task = client.mutateGraphWith(mutation) { result, error in
if let err = error {
self.displayError(ShoppingData.shopifyErrorMessage(err: err))
return
}
if let userError = result?.checkoutCreate?.userErrors, userError.count > 0 {
for err in userError {
self.displayError(err.message)
}
return
}
let checkoutReady = result?.checkoutCompleteWithCreditCard?.checkout.ready ?? false
let paymentReady = result?.checkoutCompleteWithCreditCard?.payment?.ready ?? false
print(“Checkout completed: (checkoutReady)”)
print(“Payment completed: (paymentReady)”)
}
task.resume()
}
}
============ ShoppingData.swift =================
class ShoppingData {
static var shopifyClient: Graph.Client? // endpoint to shopify store
static var shopifyCard: Card.Client? // endpoint to card processing
static var itemCollections : [CatalogCollection] = // all items in the catalog
static var cartItems : [CatalogItem] = // all items in the shopping cart
static var checkout: Storefront.Checkout? // the checkout object (from which the checkoutId is pulled from)
static var billToAddress: Storefront.MailingAddressInput? // save the address to re-use in the payment submission (in real life the bill-to may be different though)
static var cardVaultUrl: URL? // the URL used to obtain credit card tokens
static func shopifyErrorMessage(err: Graph.QueryError) → String {
switch err {
case .request(let error):
return error?.localizedDescription ?? “Error has nil local description”
case .http(let statusCode):
return HTTPURLResponse.localizedString(forStatusCode: statusCode)
case .noData:
return err.localizedDescription
case .jsonDeserializationFailed:
return “json deserialization failed”
case .invalidJson:
return “invalid json”
case .invalidQuery(let reasons):
return reasons[0].message
case .schemaViolation(let schemaError):
return “schema error: (schemaError.localizedDescription)”
}
}
}