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