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