Skip to content

Commit

Permalink
Merge pull request #1037 from stripe-ios/sgrant/BOUNCER-953
Browse files Browse the repository at this point in the history
BOUNCER-953 [StripeCardScan iOS] Remove the outer base64 encoding layer in the verification payload
  • Loading branch information
sgrant-stripe authored Apr 27, 2022
2 parents 7dc8d8f + a35b822 commit fa7996b
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import UIKit

struct VerificationFramesData: Encodable {
/// A base64 encoding of a scanned card image
let imageData: String
let imageData: Data
/// The bounds of the card view finder (measured in pixels)
let viewfinderMargins: ViewFinderMargins
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,22 +34,23 @@ extension STPAPIClient {
verificationFramesData: [VerificationFramesData]
) -> Promise<EmptyResponse> {
do {
/// TODO: Replace this with writing the JSON to a string instead of a data
/// Encode the array of verification frames data into JSON
let jsonEncoder = JSONEncoder()
jsonEncoder.keyEncodingStrategy = .convertToSnakeCase
let jsonVerificationFramesData = try jsonEncoder.encode(verificationFramesData)
/// Turn the JSON data into a base64 string
let b64VerificationFramesData = jsonVerificationFramesData.base64EncodedString()

/// Turn the JSON data into a string
let verificationFramesDataString = String(data: jsonVerificationFramesData, encoding: .utf8) ?? ""

/// Create a `VerifyFrames` object
let verifyFrames = VerifyFrames(
clientSecret: cardImageVerificationSecret,
verificationFramesData: b64VerificationFramesData
verificationFramesData: verificationFramesDataString
)

return self.submitVerificationFrames(cardImageVerificationId: cardImageVerificationId, verifyFrames: verifyFrames)
} catch(let error){
} catch(let error) {
let promise = Promise<EmptyResponse>()
promise.reject(with: error)
return promise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@ internal struct ImageConfig {
extension ScannedCardImageData {
/// Returns a `VerificationFramesData` object from the scanned image data
func toVerificationFramesData(imageConfig: ImageConfig = ImageConfig()) -> VerificationFramesData {
let encodedImage = toBase64EncodedImageData(image: previewLayerImage, imageConfig: imageConfig)
let b64ImageData = encodedImage.encodedImageData
let encodedImage = toExpectedImageFormat(image: previewLayerImage, imageConfig: imageConfig)
let imageData = encodedImage.imageData
let size = encodedImage.encodedImageSize

// make sure to adjust the size of our viewFinderRect if jpeg conversion resized the image
let scaleX = size.width / CGFloat(previewLayerImage.width)
let scaleY = size.height / CGFloat(previewLayerImage.height)
let viewfinderMargins = toViewfinderMargins(viewfinderRect: previewLayerViewfinderRect, scaleX: scaleX, scaleY: scaleY)

return VerificationFramesData(imageData: b64ImageData, viewfinderMargins: viewfinderMargins)
return VerificationFramesData(imageData: imageData, viewfinderMargins: viewfinderMargins)
}

/// Converts a CGImage into a base64 encoded string of a jpeg image
private func toBase64EncodedImageData(image: CGImage, imageConfig: ImageConfig) -> (encodedImageData: String, encodedImageSize: CGSize) {
private func toExpectedImageFormat(image: CGImage, imageConfig: ImageConfig) -> (imageData: Data, encodedImageSize: CGSize) {
/// Convert CGImage to UIImage
let convertedUIImage = UIImage(cgImage: image)

Expand All @@ -46,7 +46,7 @@ extension ScannedCardImageData {
compressionQuality: imageConfig.jpegCompressionQuality
)

return (encodedImageData: compressedImage.imageData.base64EncodedString(), encodedImageSize: compressedImage.imageSize)
return (imageData: compressedImage.imageData, encodedImageSize: compressedImage.imageSize)
}

/// Converts the view finder CGRect into a ViewFinderMargins object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,18 +172,21 @@ class STPAPIClient_CardImageVerificationTest: APIStubbedTestCase {
*/
func testSubmitVerificationFrames_Expanded() throws {
let verificationFrameData = VerificationFramesData(
imageData: "image_data",
imageData: "image_data".data(using: .utf8)!,
viewfinderMargins: ViewFinderMargins(left: 0, upper: 0, right: 0, lower: 0)
)

let mockResponse = "{}".data(using: .utf8)!

/// The list of verification frame datas are encoded with snake_case before converting to a `VerifyFrames` object
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let b64JsonEncodedVerificationFrames = try encoder.encode([verificationFrameData]).base64EncodedString()
/// Form data is made into a query string when making POST request
let b64QueryEncodedVerificationFrames = URLEncoder.string(byURLEncoding: b64JsonEncodedVerificationFrames)
let jsonEncoder = JSONEncoder()
jsonEncoder.keyEncodingStrategy = .convertToSnakeCase
let jsonVerificationFramesData = try jsonEncoder.encode([verificationFrameData])

/// Turn the JSON data into a string
let verificationFramesDataString = String(data: jsonVerificationFramesData, encoding: .utf8) ?? ""

let urlEncodedString = URLEncoder.string(byURLEncoding: verificationFramesDataString)

// Stub the request to submit verify frames
stub { request in
Expand All @@ -194,7 +197,7 @@ class STPAPIClient_CardImageVerificationTest: APIStubbedTestCase {

XCTAssertNotNil(request.url)
XCTAssertEqual(request.url?.absoluteString.contains("v1/card_image_verifications/\(CIVIntentMockData.id)/verify_frames"), true)
XCTAssertEqual(String(data: httpBody, encoding: .utf8), "client_secret=\(CIVIntentMockData.clientSecret)&verification_frames_data=\(b64QueryEncodedVerificationFrames)")
XCTAssertEqual(String(data: httpBody, encoding: .utf8), "client_secret=\(CIVIntentMockData.clientSecret)&verification_frames_data=\(urlEncodedString)")
XCTAssertEqual(request.httpMethod, "POST")

return true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ class ScanStatsPayloadAPIBindingsTests: XCTestCase {
/// Check that all the device info exists
XCTAssertTrue(queryString.contains("payload[configuration][strict_mode_frames]=5"), "configuration: strict mode frames is incorrect")
/// Check that all the device info exists
#if arch(x86_64)
XCTAssertTrue(queryString.contains("payload[device][device_type]=x86_64"), "device: device type in query string is incorrect")
#elseif arch(arm64)
XCTAssertTrue(queryString.contains("payload[device][device_type]=arm64"), "device: device type in query string is incorrect")
#endif
XCTAssertTrue(queryString.contains("payload[device][device_id]=Redacted"), "device: device id in query string dne")
XCTAssertTrue(queryString.contains("payload[device][os_version]="), "device: os version in query string is incorrect")
XCTAssertTrue(queryString.contains("payload[device][platform]=iOS"), "device: platform in query string is incorrect")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ class VerifyFramesAPIBindingsTests: XCTestCase {
}
*/
func testVerificationFramesData() throws {
let testData = "image_data".data(using: .utf8)!

let verificationFramesData = VerificationFramesData(
imageData: "image_data",
imageData: testData,
viewfinderMargins: ViewFinderMargins(
left: 0,
upper: 0,
Expand All @@ -60,7 +62,7 @@ class VerifyFramesAPIBindingsTests: XCTestCase {
let jsonDictionary = try verificationFramesData.encodeJSONDictionary()
let jsonDictionaryViewfinderMargins = jsonDictionary["viewfinder_margins"] as! [String: Any]

XCTAssertEqual(jsonDictionary["image_data"] as! String, "image_data")
XCTAssertEqual(jsonDictionary["image_data"] as! String, "aW1hZ2VfZGF0YQ==")
XCTAssertEqual(jsonDictionaryViewfinderMargins["left"] as! Int, 0)
XCTAssertEqual(jsonDictionaryViewfinderMargins["upper"] as! Int, 0)
XCTAssertEqual(jsonDictionaryViewfinderMargins["right"] as! Int, 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CardImageVerificationControllerTests: APIStubbedTestCase {
private var verificationSheetController: CardImageVerificationController!

private let mockVerificationFrameData = VerificationFramesData(
imageData: "image_data",
imageData: "image_data".data(using: .utf8)!,
viewfinderMargins: ViewFinderMargins(left: 0, upper: 0, right: 0, lower: 0)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ImageCompressionTests: XCTestCase {

let scannedCard = ScannedCardImageData(previewLayerImage: image, previewLayerViewfinderRect: roiRectangle)
let verificationFrame = scannedCard.toVerificationFramesData()
let imageData = Data(base64Encoded: verificationFrame.imageData)!
let imageData = verificationFrame.imageData
let newImage = UIImage(data: imageData)!
XCTAssertEqual(newImage.size, originalImageSize)
XCTAssertTrue(verificationFrame.viewfinderMargins.equal(to: roiRectangle))
Expand All @@ -43,7 +43,7 @@ class ImageCompressionTests: XCTestCase {
let scannedCard = ScannedCardImageData(previewLayerImage: image, previewLayerViewfinderRect: roiRectangle)
let imageConfig = ImageConfig(jpegMaxBytes: 100_000)
let verificationFrame = scannedCard.toVerificationFramesData(imageConfig: imageConfig)
let imageData = Data(base64Encoded: verificationFrame.imageData)!
let imageData = verificationFrame.imageData
let newImage = UIImage(data: imageData)!
XCTAssertNotEqual(newImage.size, originalImageSize)
let scaleX = newImage.size.width / originalImageSize.width
Expand Down

0 comments on commit fa7996b

Please sign in to comment.