Wiki source code of Payment API Client

Last modified by Thomas Warren on 2020/01/23 13:42
Hide last authors
Thomas Warren 7.1 1 ## Prerequisites
Thomas Warren 3.1 2
Thomas Warren 7.1 3 ---
Thomas Warren 3.1 4
Thomas Warren 7.1 5 * Java 11
6 * VueJS
7 * Maven
8 * Postgres
Thomas Warren 3.1 9
Thomas Warren 7.2 10 ## GitHub
11 ---
12
Thomas Warren 7.4 13 * [Payment Api Client](https://github.com/PayEx/vas-payment-api-client)
Thomas Warren 7.2 14
Thomas Warren 7.3 15 ## Swagger documentation
16
17 ---
18
19 * [Payment API Swagger](https://stage-evc.payex.com/payment-api/swagger-ui.html)
20
Thomas Warren 7.1 21 ## Project setup
Thomas Warren 3.1 22
Thomas Warren 7.1 23 ---
Thomas Warren 3.1 24
Thomas Warren 7.1 25 vas-payment-api-client
Thomas Warren 6.1 26 ├─┬ backend → backend module with Spring Boot code
27 │ ├── src
28 │ └── pom.xml
29 ├─┬ frontend → frontend module with Vue.js code
30 │ ├── src
31 │ └── pom.xml
Thomas Warren 7.1 32 └── pom.xml → Maven parent pom managing both modules
Thomas Warren 3.1 33
Thomas Warren 7.1 34 ## Security
Thomas Warren 3.1 35
Thomas Warren 7.1 36 ---
Thomas Warren 3.1 37
Thomas Warren 7.1 38 <details>
39 <summary>Oauth2:</summary>
Thomas Warren 3.1 40
Thomas Warren 7.1 41 VasPublicPaymentApi requires an OAuth2 access token for interaction.
42 This application automatically handles token fetching and refreshing by using [Spring Security](https://docs.spring.io/spring-security-oauth2-boot/docs/current/reference/htmlsingle/#boot-features-security-custom-user-info-client).
43 Configuration values are set in [application.yml](https://github.com/PayEx/vas-payment-api-client/blob/master/backend/src/main/resources/application.yml):
Thomas Warren 3.1 44
Thomas Warren 7.1 45 ```yaml
Thomas Warren 6.1 46 # "XXX" Should be replaced by value provided by PayEx
47 # CLIENT_ID/CLIENT_SECRET/VAS_AUTH_SERVER_URL can also be set in docker-compose.yml as environment variables if running with docker
48 # The application will see if environment variables are present, if not fall back to "XXX" values.
49 vas-payment-api:
50 oauth2:
51 client:
52 grantType: client_credentials
53 clientId: "${CLIENT_ID}:XXX"
54 clientSecret: "${CLIENT_SECRET}:XXX"
55 accessTokenUri: "${VAS_AUTH_SERVER_URL}:XXX"
Thomas Warren 7.1 56 scope: publicapi
57
58 ```
Thomas Warren 3.1 59
Thomas Warren 7.1 60 And the implementation of these are located in [Oauth2RestTemplateConfiguration.java](https://github.com/PayEx/vas-payment-api-client/blob/master/backend/src/main/java/com/payex/vas/demo/config/security/Oauth2RestTemplateConfiguration.java):
Thomas Warren 3.1 61
Thomas Warren 7.1 62 ```java
Thomas Warren 6.1 63 public class Oauth2RestTemplateConfiguration {
Thomas Warren 7.1 64 //...
Thomas Warren 6.1 65 @Bean
66 @ConfigurationProperties("vas-payment-api.oauth2.client")
67 protected ClientCredentialsResourceDetails oAuthDetails() {
68 return new ClientCredentialsResourceDetails();
Thomas Warren 7.1 69 }
Thomas Warren 3.1 70
Thomas Warren 7.1 71 @Bean
Thomas Warren 6.1 72 protected RestTemplate restTemplate() {
73 var restTemplate = new OAuth2RestTemplate(oAuthDetails());
Thomas Warren 7.1 74 restTemplate.setInterceptors(ImmutableList.of(externalRequestInterceptor()));
Thomas Warren 6.1 75 restTemplate.setRequestFactory(httpRequestFactory());
76 return restTemplate;
77 }
Thomas Warren 7.1 78 //...
Thomas Warren 6.1 79 }
80 ```
Thomas Warren 7.1 81 </details>
Thomas Warren 3.1 82
Thomas Warren 7.1 83 <details>
Thomas Warren 3.1 84
Thomas Warren 7.1 85 <summary>HMAC:</summary>
Thomas Warren 3.1 86
Thomas Warren 7.1 87 The API also requires HMAC authentication to be present in a request.
88 In this client the HMAC value is automatically calculated by [HmacSignatureBuilder.java](https://github.com/PayEx/vas-payment-api-client/blob/master/backend/src/main/java/com/payex/vas/demo/config/security/HmacSignatureBuilder.java) and added to all outgoing requests in [ExternalRequestInterceptor.java](https://github.com/PayEx/vas-payment-api-client/blob/master/backend/src/main/java/com/payex/vas/demo/config/ExternalRequestInterceptor.java)
Thomas Warren 3.1 89
Thomas Warren 7.1 90 HMAC is implemented using SHA-512 secure hash algorithm.
Thomas Warren 3.1 91
Thomas Warren 7.1 92 Expected `Hmac` header format is:
Thomas Warren 3.1 93
Thomas Warren 7.1 94 ```text
95 HmacSHA512 <user>:<nonce>:<digest>
96 ```
Thomas Warren 3.1 97
Thomas Warren 7.1 98 where `digest` is a Base64 formatted HMAC SHA512 digest of the following string:
Thomas Warren 3.1 99
Thomas Warren 7.1 100 ```text
Thomas Warren 6.1 101 METHOD\n
102 RESOURCE\n
103 USER\
104 NONCE\n
105 DATE\n
106 PAYLOAD\n
Thomas Warren 7.1 107 ```
Thomas Warren 3.1 108
Thomas Warren 7.1 109 `METHOD` (mandatory) the requested method (in upper case) `RESOURCE` (mandatory) the path to desired resource (without hostname and any query parameters)
110 `NONSE` (mandatory) a unique value for each request ([UUID](https://tools.ietf.org/rfc/rfc4122.txt)) `DATE`(optional) same as `Transmission-Time` if provided as seperate header. Uses [ISO8601 standard](https://en.wikipedia.org/wiki/ISO_8601) `PAYLOAD` (optional) body of request
Thomas Warren 3.1 111
Thomas Warren 7.1 112 Example request:
Thomas Warren 3.1 113
Thomas Warren 7.1 114 ```bash
Thomas Warren 6.1 115 curl -X POST \
Thomas Warren 7.1 116 https://stage-evc.payex.com/payment-api/api/payments/payment-account/balance \
Thomas Warren 6.1 117 -H 'Accept: */*' \
118 -H 'Agreement-Merchant-Id: XXX' \
119 -H 'Authorization: Bearer XXX' \
120 -H 'Hmac: HmacSHA512 user:21a0213e-30eb-85ab-b355-a310d31af30e:oY5Q5Rf1anCz7DRm3GyWR0dvJDnhl/psylfnNCn6FA0NOrQS3L0fvyUsQ1IQ9gQPeLUt9J3IM2zwoSfZpDgRJA==' \
121 -H 'Transmission-Time: 2019-06-18T09:19:15.208257Z' \
122 -H 'Session-Id: e0447bd2-ab64-b456-b17b-da274bb8428e' \
123 -d '{
124 "accountIdentifier": {
125 "accountKey": "7013369000000000000",
126 "cvc": "123",
127 "expiryDate": "2019-12-31",
128 "instrument": "GC"
129 }
130 }'
Thomas Warren 7.1 131 ```
Thomas Warren 3.1 132
Thomas Warren 7.1 133 In this example `USER` is user and `SECRET` is secret.
Thomas Warren 3.1 134
Thomas Warren 7.1 135 The plain string to `digest` would then be:
Thomas Warren 3.1 136
Thomas Warren 7.1 137 ```text
Thomas Warren 6.1 138 POST
139 /payment-api/api/payments/payment-account/balance
140 user
141 21a0213e-30eb-85ab-b355-a310d31af30e
142 2019-06-18T09:19:15.208257Z
143 {
144 "accountIdentifier": {
145 "accountKey": "7013360000000000000",
146 "cvc": "123",
147 "expiryDate": "2020-12-31",
148 "instrument": "CC"
149 }
150 }
Thomas Warren 7.1 151 ```
Thomas Warren 3.1 152
Thomas Warren 7.1 153 The plain `digest` string is then hashed with `HmacSHA512` algorithm and the `SECRET`. Finally we Base64 encode the hashed value. This is the final `digest` to be provided in the `Hmac` header.
Thomas Warren 3.1 154
Thomas Warren 7.1 155 Final `Hmac` header value:
Thomas Warren 3.1 156
Thomas Warren 7.1 157 ```text
Thomas Warren 6.1 158 HmacSHA512 user:21a0213e-30eb-85ab-b355-a310d31af30e:oY5Q5Rf1anCz7DRm3GyWR0dvJDnhl/psylfnNCn6FA0NOrQS3L0fvyUsQ1IQ9gQPeLUt9J3IM2zwoSfZpDgRJA==
Thomas Warren 7.1 159 ```
Thomas Warren 3.1 160
Thomas Warren 7.1 161 #### Postman example script
Thomas Warren 3.1 162
Thomas Warren 7.1 163 In pre-request script copy/paste the following snippet:
Thomas Warren 3.1 164
Thomas Warren 7.1 165 ```javascript
Thomas Warren 3.1 166
Thomas Warren 7.1 167 var user = 'user';
Thomas Warren 6.1 168 var secret = 'secret';
169 var transmissionTime = (new Date()).toISOString();
Thomas Warren 7.1 170 var sessionId = guid();
Thomas Warren 3.1 171
Thomas Warren 7.1 172 var hmac = generateHMAC(user, secret, transmissionTime);
173 console.log('hmac: ' + hmac);
Thomas Warren 3.1 174
Thomas Warren 7.1 175 //Set header values
Thomas Warren 6.1 176 pm.request.headers.add({key: 'Hmac', value: hmac });
177 pm.request.headers.add({key: 'Transmission-Time', value: transmissionTime });
Thomas Warren 7.1 178 pm.request.headers.add({key: 'Session-Id', value: sessionId });
Thomas Warren 3.1 179
Thomas Warren 7.1 180 function generateHMAC(user, secret, transmissionTime) {
Thomas Warren 3.1 181
Thomas Warren 7.1 182 var algorithm = "HmacSHA512";
Thomas Warren 6.1 183 var separator = ":";
184 var method = request.method.toUpperCase();
Thomas Warren 7.1 185 var nonce = generateNonce(); //UUID
Thomas Warren 6.1 186 var date = transmissionTime;
Thomas Warren 7.1 187
188 var uri_path = replaceRequestEnv(request.url.trim()).trim().replace(new RegExp('^https?://[^/]+/'), '/'); // strip hostname
189 uri_path = uri_path.split("?")[0]; //Remove query paramters
Thomas Warren 6.1 190 var payload = _.isEmpty(request.data) ? "" : request.data;
191 var macData = method + '\n'
192 + uri_path + '\n'
193 + user + '\n'
194 + nonce + '\n'
195 + date + '\n'
Thomas Warren 7.1 196 + payload + '\n';
Thomas Warren 3.1 197
Thomas Warren 7.1 198 macData = replaceRequestEnv(macData);
199 console.log('data to mac: ' + macData);
Thomas Warren 3.1 200
Thomas Warren 7.1 201 var hash = CryptoJS.HmacSHA512(macData, secret);
Thomas Warren 6.1 202 var digest = CryptoJS.enc.Base64.stringify(hash);
203 return algorithm + " " + user + separator + nonce + separator + digest;
Thomas Warren 7.1 204 }
Thomas Warren 3.1 205
Thomas Warren 7.1 206 function replaceRequestEnv(input) { //manually set environments to they are populated before hashing
207 return input.replace(/{{([^)]+)}}/g, function (str, key) {
Thomas Warren 6.1 208 var value = pm.environment.get(key);
209 return value === null ? pm.varables.get(key) : value;
210 });
Thomas Warren 7.1 211 }
Thomas Warren 3.1 212
Thomas Warren 7.1 213 function generateNonce() {
Thomas Warren 6.1 214 return guid();
Thomas Warren 7.1 215 }
Thomas Warren 3.1 216
Thomas Warren 7.1 217 function guid() {
Thomas Warren 6.1 218 function s4() {
219 return Math.floor((1 + Math.random()) * 0x10000)
220 .toString(16)
221 .substring(1);
Thomas Warren 7.1 222 }
Thomas Warren 3.1 223
Thomas Warren 7.1 224 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
Thomas Warren 6.1 225 s4() + '-' + s4() + s4() + s4();
Thomas Warren 7.1 226 }
Thomas Warren 3.1 227
Thomas Warren 7.1 228 ```
229 </details>
Thomas Warren 3.1 230
Thomas Warren 7.1 231 ### Security documentation
Thomas Warren 3.1 232
Thomas Warren 7.1 233 ---
Thomas Warren 3.1 234
Thomas Warren 7.1 235 * [OAuth2](https://oauth.net/2/)
236 * [Client Credentials](https://www.oauth.com/oauth2-servers/access-tokens/client-credentials/)
237 * [The RESTful CookBook: HMAC](http://restcookbook.com/Basics/loggingin/)
238 * [HMAC - Wikipedia](https://en.wikipedia.org/wiki/HMAC)
Thomas Warren 3.1 239
Thomas Warren 7.1 240 ## First App run
Thomas Warren 3.1 241
Thomas Warren 7.1 242 ---
Thomas Warren 3.1 243
Thomas Warren 7.1 244 **NB! The application expects a PostgreSQL server to be running on localhost with a username `test` and password `test` to exist.**
245 **This can automatically be configured if PostgreSQL server is started in docker with environment variables `POSTGRES_USER=test` and `POSTGRES_PASSWORD=test` are set (See [docker-compose.yml](https://github.com/PayEx/vas-payment-api-client/blob/master/docker-compose.yml)).**
Thomas Warren 3.1 246
Thomas Warren 7.1 247 Inside the root directory, do a:
Thomas Warren 3.1 248
Thomas Warren 7.1 249 ```bash
Thomas Warren 6.1 250 mvn clean install
Thomas Warren 7.1 251 ```
Thomas Warren 3.1 252
Thomas Warren 7.1 253 Run the Spring Boot App:
Thomas Warren 3.1 254
Thomas Warren 7.1 255 ```bash
256 mvn --projects backend spring-boot:run
257 ```
Thomas Warren 3.1 258
Thomas Warren 7.1 259 Now go to <http://localhost:8080/> and have a look at your new client.
Thomas Warren 3.1 260
Thomas Warren 7.1 261 ## Testing application
Thomas Warren 3.1 262
Thomas Warren 7.1 263 ---
Thomas Warren 3.1 264
Thomas Warren 7.1 265 1. Add a new card with provided details from PayEx.
266 1. Click on newly added Card
267 1. Click on "initiate payment" to create a new transaction
Thomas Warren 3.1 268
Thomas Warren 7.1 269 ## Build docker image:
Thomas Warren 3.1 270
Thomas Warren 7.1 271 ---
Thomas Warren 3.1 272
Thomas Warren 7.1 273 ```bash
274 mvn --projects backend clean compile jib:dockerBuild
275 ```
Thomas Warren 3.1 276
Thomas Warren 7.1 277 ## Deploy to local docker:
Thomas Warren 3.1 278
Thomas Warren 7.1 279 ---
Thomas Warren 3.1 280
Thomas Warren 7.1 281 ```bash
Thomas Warren 6.1 282 docker-compose up -d
Thomas Warren 7.1 283 ```

Tips

You can click on the arrows next to the breadcrumb elements to quickly navigate to sibling and children pages.

Need help?

If you need help with XWiki you can contact: