Apple in app provisioning 'Could not add card'

Viewed 961

I am implementing apple in-app provisioning and I follow all steps in the apple guide but in the end, I get a message 'Could not add card' but don't have any error throw this process. This is how I create PKAddPaymentPassViewController

      let cardInfoPass  = PKAddPaymentPassRequestConfiguration.init(encryptionScheme: PKEncryptionScheme.ECC_V2);
  cardInfoPass?.cardholderName = cardholderName as? String; //The name of the person as shown on the card.
  cardInfoPass?.primaryAccountSuffix = primaryAccountSuffix as? String; //The last four or five digits of the card’s number.
  cardInfoPass?.localizedDescription = localizedDescription as? String; //A short description of the card.
  cardInfoPass?.paymentNetwork = PKPaymentNetwork.masterCard;
  
  cardInfoPass?.primaryAccountIdentifier = primaryAccountIdentifier as? String; // A primary account identifier, used to filter out pass libraries.

cardholderName is the name written on the card primaryAccountSuffix last 4 digit written on the card localizedDescription bank name paymentNetwork we are using master card primaryAccountIdentifier it is number from iTunes something light this 1MNJDDA667.com.bank.package.name

I think this part is correct I can open the apple wallet modal and all this data are there but when I continue in a modal on the end I need to get certificate and send this certificate to our BE and be should send me back 3 values and they send it to me

...
  let certificateLeaf = certificates[0].base64EncodedString();
  let certificateSubCA = certificates[1].base64EncodedString();
  let nonceString = nonce.base64EncodedString();
  let nonceSignature = nonceSignature.base64EncodedString();
  ...
  let reqDataDic: [String: Any]  = [
    "cardId": cardId,
    "applePublicCertificate": certificateSubCA,
    "nonce": nonceString,
    "nonceSignature": nonceSignature,
    "customerId": customerId,
    "deviceId": deviceId,
  ]
  ....
  var request = URLRequest(url: url)
  request.httpMethod = "POST"
  ....
  request.httpBody = try? JSONSerialization.data(withJSONObject: reqDataDic, options: .prettyPrinted)

UPDATE2: we are now sending nonce and nonceSignature as HEX like this

extension Data {
    struct HexEncodingOptions: OptionSet {
        let rawValue: Int
        static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
    }

    func hexEncodedString(options: HexEncodingOptions = []) -> String {
        let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
        return self.map { String(format: format, $0) }.joined()
    }
}
...
      let nonceData = Data(bytes: nonce)
  let nonceHex = nonceData.hexEncodedString();
  
  let nonceSignatureData = Data(bytes: nonceSignature)
  let nonceSignatureHex = nonceSignatureData.hexEncodedString();

BE send me back all values that I need: activationData, ephemeralPublicKey, encryptedPassData it returns it as a JSON object so I need to convert it to Data and all these values put into handler

this is how I am putting data to handler:

          if let dictionaryJson = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any] {

        let activationDataString = dictionaryJson["activationData"] as! String;
        let ephemeralPublicKeyString = dictionaryJson["ephemeralPublicKey"] as! String;
        let encryptedPassDataString = dictionaryJson["encryptedPassData"] as! String;

        let activationData = activationDataString.data(using: .utf8)
        let ephemeralPublicKey  = Data(base64Encoded: ephemeralPublicKeyString)
        let encryptedPassData = Data(base64Encoded: encryptedPassDataString)
        
        let paymentPassRequest = PKAddPaymentPassRequest.init()

        paymentPassRequest.activationData = activationData;
        paymentPassRequest.encryptedPassData = encryptedPassData;
        paymentPassRequest.ephemeralPublicKey = ephemeralPublicKey;

        handler(paymentPassRequest)
     }

I fill all data into paymentPassRequest and all looks ok xCode is not complaining. And at this moment apple wallet shows an alert dialog with Could not add a card with 2 buttons try it later or try it again ....

  • I have a card whitelisted on the MasterCard side
  • I tried it on simulators, real devices, and also on app in TestFlight

UPDATE:

We found an error from the Apple

Response:

https://nc-pod4-smp-device.apple.com:443/broker/v4/devices/042D1xxxxxxxxxxxxx2C52/cards 500

  {
    Connection = close;
    "Content-Length" = 81;
    "Content-Type" = "application/json";
    Date = "Thu, 08 Jul 2021 08:35:25 GMT";
    Vary = "accept-language";
    "X-Pod" = "nc-pod4";
    "X-Pod-Region" = "paymentpass.com.apple";
    "x-conversation-id" = b2axxxxxxxxxxx9e6a4d;
  }
  {
    statusCode = 500;
    statusMessage = "Broker Service Response exception";
  }
3 Answers

you are encoding nonce, nonce signature with Hex format for sending it to your server, and after getting the response back, you are trying to convert them with base64 and utf8. Try with Hex, it should work.

We are using the below conversions

- (NSData *)dataFromHexString:(NSString *)string
{
    string = [string lowercaseString];
    NSMutableData *data= [NSMutableData new];
    unsigned char whole_byte;
    char byte_chars[3] = {'\0','\0','\0'};
    int i = 0;
    int length = string.length;
    while (i < length-1) {
        char c = [string characterAtIndex:i++];
        if (c < '0' || (c > '9' && c < 'a') || c > 'f')
            continue;
        byte_chars[0] = c;
        byte_chars[1] = [string characterAtIndex:i++];
        whole_byte = strtol(byte_chars, NULL, 16);
        [data appendBytes:&whole_byte length:1];
    }
    return data;
}

-(NSMutableString *) convertToString:(NSData *)data{
  NSUInteger capacity = data.length * 2;
  NSMutableString *sbuf = [NSMutableString stringWithCapacity:capacity];
  const unsigned char *buf = data.bytes;
  NSInteger i;
  for (i=0; i<data.length; ++i) {
    [sbuf appendFormat:@"%02x", (NSUInteger)buf[i]];
  }
  return  sbuf;
}

let activationData = activationDataString.data(using: .utf8)

I think this encoded is only for VISA.

For MasterCard it has to be base64:

let activationData = Data(base64Encoded: activationDataString)

I ran into this exact issue implementing our Card Provisioning. In our case we had to do both of the following:

  1. Make sure our PKAddPaymentPassRequest fields were all set properly. All three of the fields we were provided by our API (activationData, encryptedPassData, ephemeralPublicKey) were base64 encoded so they all had to be converted to Data's as such: paymentPassRequest.ephemeralPublicKey = Data(base64Encoded: <YOUR EPHEMERAL PUBLIC KEY STRING>, options: [])

  2. We had to create new TestFlight builds in order to fully test this workflow. I ran into that exact same 500 response from Apple anytime I tried profiling or running the app from Xcode directly, even if it was on a physical device. It wasn't until I ran the TestFlight build that it finally worked properly.

Related