We have a spring boot backend trying to perform merchant validation and retrieve a payment session for Apple Pay. Following these steps from Apple, we were able to generate a merchant validation certificate. When I try to create a Keystore programmatically and add the certificate issued by Apple for use in my web client call, I get the following error:
Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Here is the code snippet:
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
val certificateFactory = CertificateFactory.getInstance("X.509")
val keyStore: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType())
keyStore.load(null)
val merchantValidationInputStream = FileInputStream(<PATH_TO_CERT_FROM_APPLE>)
val merchantValidationCert= certificateFactory.generateCertificate(merchantValidationInputStream)
keyStore.setCertificateEntry("applePayMerchantValidationCert", merchantValidationCert)
val sslContext = SslContextBuilder
.forClient()
.trustManager(trustManagerFactory)
.build()
val httpClient = HttpClient.create()
.secure { it.sslContext(sslContext) }
val webClient = builder
.clientConnector(ReactorClientHttpConnector(httpClient))
.baseUrl(<VALIDATION_URL_PASSED_FROM_WEB>)
.build()
return webClient.post()
.bodyValue(<REQUEST_BODY_WITH_MERCHANTID_DISPLAYNAME_INITIATIVE_INITIATIVE_CONTEXT>)
.retrieve()
.toBodilessEntity()
// For now I am just trying to get this call working and not doing anything with the response.
// Using https://developer.apple.com/documentation/apple_pay_on_the_web/apple_pay_js_api/requesting_an_apple_pay_payment_session#3199965
// as reference for the request body structure
Could I get some help figuring out what might be happening here?
Tools/Framework Versions
Things I have tried:
After working on this with @janani-subbiah we determined the following was the solution.
For full instructions please visit the Apple Pay docs but I wanted to give a high level overview of the cert transitions we needed to make as well as the Spring Boot code to use it. Also note, you can do most of the cert changes with OpenSSL but I just used the Mac Keychain tool for simplicity.
data class PaymentSessionRequest(
val merchantIdentifier: String = "merchant.your merchant identifier",
val displayName: String = "Any Apple Pay Display Name",
val initiative: String = "web",
val initiativeContext: String = "Fully qualified domain of the frontend"
)
val pass = "Password you set when exporting the p12 cert in step 8" // Should be set as a secret and exposed as an environment variable.
val inputStream = javaClass.getResourceAsStream("/merchant_id.p12") // Should be mounted as a secret file and loaded from there.
val keyStore = KeyStore.getInstance("PKCS12")
keyStore.load(inputStream, pass.toCharArray())
val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(keyStore, pass.toCharArray())
val context = SslContextBuilder.forClient()
.keyManager(keyManagerFactory)
.build()
val client: HttpClient = HttpClient.create().secure { spec -> spec.sslContext(context) }
val connector: ClientHttpConnector = ReactorClientHttpConnector(client)
val webClient = WebClient.builder()
.baseUrl("https://apple-pay-gateway.apple.com") // Should use validation URL passed in from the Apple Pay JS SDK and include IP address validation based on Apple's docs
.clientConnector(connector)
.build()
webClient.post()
.uri("/paymentservices/paymentSession")
.bodyValue(PaymentSessionRequest())
.retrieve()
.bodyToMono<String>()
.doOnNext { logger.info("Apple Pay Session response: $it") }