Skip to content

Commit cd8c2ab

Browse files
committed
Update Java SpringBoot example for VISA Developer Platform
1 parent ec3f579 commit cd8c2ab

18 files changed

Lines changed: 374 additions & 262 deletions

java/spring-boot/.gitignore

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,42 @@
1-
# Covers all spring-boot ignore files
2-
# Reference: https://github.com/spring-projects/spring-boot/blob/master/.gitignore
3-
4-
.gradle
5-
*.sw?
6-
.#*
7-
*#
8-
*~
9-
/build
10-
/code
11-
.classpath
12-
.project
13-
.settings
14-
.metadata
15-
.factorypath
16-
.recommenders
17-
bin
18-
build
19-
lib/
20-
target
21-
.factorypath
22-
.springBeans
23-
interpolated*.xml
24-
dependency-reduced-pom.xml
25-
build.log
26-
_site/
27-
.*.md.html
28-
manifest.yml
29-
MANIFEST.MF
30-
settings.xml
31-
activemq-data
32-
overridedb.*
33-
*.iml
34-
*.ipr
35-
*.iws
36-
.idea
37-
*.jar
38-
.DS_Store
39-
.factorypath
40-
dump.rdb
1+
# Covers all spring-boot ignore files
2+
# Reference: https://github.com/spring-projects/spring-boot/blob/master/.gitignore
3+
4+
.gradle
5+
*.sw?
6+
.#*
7+
*#
8+
*~
9+
/build
10+
/code
11+
.classpath
12+
.project
13+
.settings
14+
.metadata
15+
.factorypath
16+
.recommenders
17+
bin
18+
build
19+
lib/
20+
target
21+
.factorypath
22+
.springBeans
23+
interpolated*.xml
24+
dependency-reduced-pom.xml
25+
build.log
26+
_site/
27+
.*.md.html
28+
manifest.yml
29+
MANIFEST.MF
30+
settings.xml
31+
activemq-data
32+
overridedb.*
33+
*.iml
34+
*.ipr
35+
*.iws
36+
.idea
37+
*.jar
38+
.DS_Store
39+
.factorypath
40+
dump.rdb
41+
42+
nbactions.xml

java/spring-boot/README.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,11 @@ A minimalist java/spring-boot example integration using Flex-API tokenization.
1010

1111
## Setup Instructions
1212

13-
1. Modify `./src/main/resources/application.properties` with the credentials provided by customer support in your private BETA invite and that your `.p12` key is placed in the directory specified.
13+
1. Modify `./src/main/resources/application.properties` with the credentials created through [VISA Developer Portal](https://developer.visa.com/).
1414

1515
```properties
16-
mid=YOUR_MID
17-
cmmKey=123456789012345678901234567890
18-
organizationId=YOUR_ORD_ID
19-
keyStoreFile=/your_keystore_file.p12
20-
keyStorePassword=YOUR_KEYSTORE_PASS
21-
privateKeyPassword=YOUR_PRIVKEY_PASS
16+
vdp.api-key=_YOUR_APPLICATION_SPECIFIC_API_KEY_
17+
vdp.shared-secret=_YOUR_APPLICATION_SPECIFIC_SHARED_SECRET_
2218
```
2319

2420
2. Build and run the application using maven
@@ -29,7 +25,7 @@ A minimalist java/spring-boot example integration using Flex-API tokenization.
2925

3026
## Tips
3127

32-
- If you are having issues, checkout the full [FLEX documentation](http://apps.cybersource.com/library/documentation/dev_guides/Secure_Acceptance_Flex/html/).
28+
- If you are having issues, checkout the full [FLEX documentation](https://developer.visa.com/products/cybersource/reference#cybersource__cybersource_flex_api).
3329

3430
- To change the port the application is served on, update `server.port=8443` in `application.properties`, replacing `8443` with your desired port number.
3531

java/spring-boot/pom.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,5 @@
4848
<groupId>org.springframework.boot</groupId>
4949
<artifactId>spring-boot-starter-web</artifactId>
5050
</dependency>
51-
<!-- bouncycastle for cryptography -->
52-
<dependency>
53-
<groupId>org.bouncycastle</groupId>
54-
<artifactId>bcprov-jdk15on</artifactId>
55-
<version>1.51</version>
56-
</dependency>
5751
</dependencies>
5852
</project>
Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
/**
2-
* Copyright (c) 2016 by CyberSource
3-
* Governing licence: https://github.com/CyberSource/cybersource-flex-samples/blob/master/LICENSE.md
4-
*/
5-
2+
* Copyright (c) 2016 by CyberSource
3+
* Governing licence: https://github.com/CyberSource/cybersource-flex-samples/blob/master/LICENSE.md
4+
*/
65
package com.cybersource.flex.application;
76

87
import com.cybersource.flex.models.KeyParameters;
8+
import com.cybersource.flex.models.KeyParametersMessageConverter;
99
import com.cybersource.flex.models.KeyResult;
10+
import com.cybersource.flex.vdp.VDPEnpoints;
1011
import java.security.PublicKey;
1112
import java.util.Map;
13+
import javax.annotation.PostConstruct;
1214
import javax.servlet.http.HttpSession;
1315
import org.springframework.beans.factory.annotation.Autowired;
1416
import org.springframework.beans.factory.annotation.Value;
15-
import org.springframework.http.HttpEntity;
16-
import org.springframework.http.HttpHeaders;
1717
import org.springframework.stereotype.Controller;
1818
import org.springframework.ui.Model;
1919
import org.springframework.web.bind.annotation.RequestMapping;
@@ -24,16 +24,23 @@
2424
@Controller
2525
public class CheckoutController {
2626

27-
@Value("${mid}")
28-
private String mid;
29-
@Value("${profileId}")
30-
private String profileId;
27+
@Value("${vdp.api-key}")
28+
private String apiKey;
29+
@Value("${vdp.shared-secret}")
30+
private String sharedSecret;
3131

32-
private final static String flexKeysEndpoint = "https://testflex.cybersource.com/cybersource/flex/v1/keys";
32+
private final FlexSecurityService flexSecurityService;
33+
private final RestTemplate restTemplate = new RestTemplate();
3334

3435
@Autowired
35-
private SecurityService securityService;
36-
private final RestTemplate restTemplate = new RestTemplate();
36+
public CheckoutController(final FlexSecurityService flexSecurityService) {
37+
this.flexSecurityService = flexSecurityService;
38+
}
39+
40+
@PostConstruct
41+
private void postConstruct() {
42+
restTemplate.getMessageConverters().add(0, new KeyParametersMessageConverter(apiKey, sharedSecret));
43+
}
3744

3845
@RequestMapping("/")
3946
String redirect() {
@@ -42,36 +49,26 @@ String redirect() {
4249

4350
@RequestMapping("/checkout")
4451
String checkout(final HttpSession session, final Model model) {
45-
// prepare HTTP headers to make server2flex rest call
46-
final HttpHeaders headers = new HttpHeaders();
47-
headers.set("X-MERCHANT-ID", mid);
48-
securityService.addSignature(headers);
52+
// retrieve one time use public RSA key from Flex to facilitate PAN encryption
53+
final KeyParameters keyParameters = new KeyParameters();
54+
KeyResult key = restTemplate.postForObject(VDPEnpoints.Sandbox.keysUrl(apiKey), keyParameters, KeyResult.class);
4955

50-
// prepare keys endpoint request payload
51-
KeyParameters requestBody = new KeyParameters();
52-
requestBody.setEncryptionType("WebCryptoAPI"); // encryption type
53-
54-
// retrieve one time use RSA public key
55-
HttpEntity<KeyParameters> httpEntity = new HttpEntity<>(requestBody, headers);
56-
KeyResult key = restTemplate.postForObject(flexKeysEndpoint, httpEntity, KeyResult.class);
57-
58-
// parse Flex public key in DER format and store it in session for future use.
59-
PublicKey flexPublicKey = securityService.decodePublicKey(key.getDer().getPublicKey());
56+
// parse Flex public key in DER format and store it in session for future use (i.e. to verify token signature)
57+
PublicKey flexPublicKey = flexSecurityService.decodePublicKey(key.getDer().getPublicKey());
6058
session.setAttribute("flexPublicKey", flexPublicKey);
6159

62-
// Add JSON Web Keystore to the view model and return rendered page
60+
// Add JSON Web Keystore to the view model and return rendered "checkout" page
6361
model.addAttribute("jwk", key.getJwk());
6462
return "checkout";
6563
}
6664

6765
@RequestMapping(value = "/receipt", method = RequestMethod.POST)
6866
String receipt(@RequestParam final Map<String, Object> postParams, final HttpSession session, final Model model) {
69-
7067
// Read in the public key to use and remove it from the session
7168
PublicKey flexPublicKey = (PublicKey) session.getAttribute("flexPublicKey");
72-
session.removeAttribute("flexPublicKey");
69+
session.removeAttribute("flexPublicKey"); // no longer needed
7370

74-
// verify Flex signature passed as POST parameters
71+
// verify Flex signature passed as POST parameter
7572
String signedFields = (String) postParams.get("flex_signedFields");
7673
StringBuilder sb = new StringBuilder();
7774
for (String k : signedFields.split(",")) {
@@ -80,14 +77,21 @@ String receipt(@RequestParam final Map<String, Object> postParams, final HttpSes
8077
}
8178
final String signedValues = sb.substring(1);
8279
final String signature = (String) postParams.get("flex_signature");
83-
if (!securityService.verify(flexPublicKey, signedValues, signature)) {
80+
if (!flexSecurityService.verify(flexPublicKey, signedValues, signature)) {
8481
throw new RuntimeException("The signature is not valid");
8582
}
8683

87-
// Add the post params to our view model
88-
model.addAttribute("postParams", postParams);
84+
/**
85+
*
86+
* The payment may now be completed using the received & validated
87+
* token.
88+
*
89+
* For demonstration purposes, all post parameters are added to the view
90+
* model to display data received from cardholder's browser.
91+
*/
8992

93+
model.addAttribute("postParams", postParams);
9094
return "receipt";
9195
}
9296

93-
}
97+
}

java/spring-boot/src/main/java/com/cybersource/flex/application/EntryPoint.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
/**
2-
* Copyright (c) 2016 by CyberSource
3-
* Governing licence: https://github.com/CyberSource/cybersource-flex-samples/blob/master/LICENSE.md
4-
*/
5-
2+
* Copyright (c) 2016 by CyberSource
3+
* Governing licence: https://github.com/CyberSource/cybersource-flex-samples/blob/master/LICENSE.md
4+
*/
65
package com.cybersource.flex.application;
76

87
import org.springframework.boot.SpringApplication;
98
import org.springframework.boot.autoconfigure.SpringBootApplication;
109

11-
import java.lang.reflect.Field;
12-
1310
@SpringBootApplication
1411
public class EntryPoint {
1512

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright (c) 2016 by CyberSource
3+
* Governing licence: https://github.com/CyberSource/cybersource-flex-samples/blob/master/LICENSE.md
4+
*/
5+
package com.cybersource.flex.application;
6+
7+
import java.security.InvalidKeyException;
8+
import java.security.KeyFactory;
9+
import java.security.NoSuchAlgorithmException;
10+
import java.security.PublicKey;
11+
import java.security.Signature;
12+
import java.security.SignatureException;
13+
import java.security.spec.InvalidKeySpecException;
14+
import java.security.spec.X509EncodedKeySpec;
15+
import java.util.Base64;
16+
import org.springframework.stereotype.Service;
17+
18+
@Service
19+
public class FlexSecurityService {
20+
21+
private static final Base64.Decoder DECODER = Base64.getDecoder();
22+
23+
public PublicKey decodePublicKey(String derEncodedKey) {
24+
try {
25+
byte[] keyBytes = DECODER.decode(derEncodedKey);
26+
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(keyBytes));
27+
return publicKey;
28+
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
29+
throw new RuntimeException(e);
30+
}
31+
}
32+
33+
public boolean verify(final PublicKey publicKey, final String dataToVerify, final String base64Signature) {
34+
try {
35+
final Signature signInstance = Signature.getInstance("SHA512withRSA");
36+
signInstance.initVerify(publicKey);
37+
signInstance.update(dataToVerify.getBytes());
38+
byte[] signature = DECODER.decode(base64Signature);
39+
return signInstance.verify(signature);
40+
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException ex) {
41+
throw new RuntimeException(ex);
42+
}
43+
}
44+
45+
}

0 commit comments

Comments
 (0)