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 ```