BUG: Access denied calling checkoutCompleteWithCreditCard from iOS App

I’m developing a proof-of-concept iOS app to help my company decide whether to use Shopify as a back-end for product sales. I’ve followed the iOS SDK guides, and have created a working application that does nearly everything it needs:

  1. Access store info

  2. Access collections and product catalog

  3. Build a shopping cart, update shipping address, use the cardVaultUrl and CardCiient to obtain a token (I’m in test mode, so using a test credit card number)

On the next step I use checkoutCompleteWithCreditCard to process the order, and receive back “access denied”.

I see on the uinversity forum that this issue comes up a lot (see links below), with no responses from Shopify whether this opertion shoudl work or not from a private app via iOS SDK.

Is this feature not supported? The documentation makes no mention of it being restricted, so it appears to be a bug.

https://ecommerce.shopify.com/c/shopify-apis-and-technology/t/checkoutcompletewithcreditcard-acess-denied-in-storefront-api-s-471882

https://ecommerce.shopify.com/c/shopify-apis-and-technology/t/how-to-complete-native-checkout-using-storefront-api-s-471475

https://ecommerce.shopify.com/c/shopify-apis-and-technology/t/storefront-api-checkoutcompletewithtokenizedpayment-429965

https://ecommerce.shopify.com/c/shopify-apis-and-technology/t/storefront-api-process-checkout-438429

4 Likes

Could you please provide a little more information on the problem to help diagnose this case? It would be helpful to see what your payload to “checkoutCompleteWithCreditCard” looks like, as well as the payload to the “vaultURL” used for vaulting the card.

Keep in mind the the vaulting service doesn’t perform any validation. This happens during the checkout process instead, which might lead to falsely believing that the vaulted token is valid and can be used for payment. Seeing your payloads will help us better understand where the problem might be.

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)”

}

}

}

It’s possible the gateway doesn’t like your test card number, verification number, etc. Have you tried a different test card?

Yes, I’m using the numbers pasted from your site (see below). I’ve tried the visa and mastercard for sure, and have the same error either way.

https://help.shopify.com/manual/payments/shopify-payments/testing-shopify-payments

just to reiterate…if the payment info was invalid, the error should be “payment declined” not “access denied”, right?

See post below

Reposting with shopify shop connected account: I’m seeing the same “access denied” error when calling checkoutCompleteWithCreditCard. Using Mobile-Buy-SDK 3.1.2 on iOS with Shopify private app storefront access token. Our test payload looks very similar to powerleydev’s. Our shopify payment is also in test mode, and the credit card info is the same test credit card number(4242424242424242). Please let me know what additional info I need to provide to figure out what’s wrong.

1 Like

Request payload for checkoutCompleteWithCreditCard:

mutation{checkoutCompleteWithCreditCard(checkoutId:“Z2lkOi8vc2hvcGlmeS9DaGVja291dC9jYTdiYTk5YTFjNTY0MmU1M2I0NTFjNjBlNWU5YzlhMj9rZXk9MjAwNDk4ZWRiYTM3NDQ2MmFjMzBiNjQyMGVmNjAxNzM=”,payment:{amount:“12.15”,idempotencyKey:“56D2826C-B45A-4F19-8182-EC6B06F3DC9C”,billingAddress:{address1:“1 Gary”,address2:“”,city:“San Francisco”,company:null,country:“United States”,firstName:“Joe”,lastName:“Black”,phone:“5101231234”,province:“California”,zip:“94110”},vaultId:“west-8c88cce51c8695bcaea71480f140ebbc”,test:true}){checkout{id,ready,requiresShipping,taxesIncluded,email,shippingAddress{firstName,lastName,phone,address1,address2,city,country,countryCode,province,provinceCode,zip},shippingLine{handle,title,price},note,lineItems(first:250){edges{cursor,node{variant{id,price},title,quantity}}},webUrl,currencyCode,subtotalPrice,totalTax,totalPrice,paymentDue,order{id,orderNumber,customerUrl,email}},userErrors{field,message}}}

Response:

{
“data”: {
“checkoutCompleteWithCreditCard”: null
},
“errors”: [{
“message”: “CheckoutCompleteWithCreditCard access denied”,
“locations”: [{
“line”: 1,
“column”: 10
}],
“path”: [“checkoutCompleteWithCreditCard”]
}]
}

1 Like

Aready treid with real credit card info and turning off payment test mode, got same error.

Is there a permission that needs to added to the private app that’s not avaiable to be set in the admin UI? If so, how do I get that set or whom should I contact? Already tried customer support, but they can’t resolve API related issues and directed me to this forum.

Please advice on next step.

Further investigation revealed that it was a misconfiguration of permissions. It has been fixed and the checkouts should be running smoothly now. Please verify that it’s working as you expect.

I’m still getting the same error. Could you fix the private app permission on spongerevolution.myshopify.com account too?

Mira, the permissions on your store have been updated.

Works now, thank you. Will newly created private app have the correct permission now?

@dima, thanks for taking care of this issue. Works great now!

I’m also getting the same error. Could you please fix the permissions for worm-app.myshopify.com account too?

@gurhan Permissions adjusted. Please take look.

Hi @Dima, I’m also facing this same error in my android proof-of-concept app. I’ve created a new thread some time ago describing all the steps I took to proceed with the checkout but I’ve got no answer. Can you please help me to figure out what I’m missing in the checkout process please? I’ve attached all the logs (requests and responses) for every single step.

As far as I understand from this thread the “Access denied” error is related to a misconfiguration of permissions in the store, is it right? If so, is there any way to set those missing permissions in the admin UI?

Please provide your shop domain that you’re using for checkout.

Hi Dima, thank you very much for your quick response. I’ve already successfully completed the checkout with credit card. I had a live chat with a colleague of yours who set the proper permissions to my private app already.

It would be very useful if the process to request permission for private app was better detailed in the documentation. Is there any way to set these permissions without having to ask a Shopify Employee? I mean is there any way I could set them myself in the Admin UI (or any other interface available to me)?.