Wiki source code of Payment API Client

Last modified by Thomas Warren on 2020/01/23 13:42
Show last authors
1 ## Prerequisites
2
3 ---
4
5 * Java 11
6 * VueJS
7 * Maven
8 * Postgres
9
10 ## GitHub
11 ---
12
13 * [Payment Api Client](https://github.com/PayEx/vas-payment-api-client)
14
15 ## Swagger documentation
16
17 ---
18
19 * [Payment API Swagger](https://stage-evc.payex.com/payment-api/swagger-ui.html)
20
21 ## Project setup
22
23 ---
24
25 vas-payment-api-client
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
32 └── pom.xml → Maven parent pom managing both modules
33
34 ## Security
35
36 ---
37
38 <details>
39 <summary>Oauth2:</summary>
40
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):
44
45 ```yaml
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"
56 scope: publicapi
57
58 ```
59
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):
61
62 ```java
63 public class Oauth2RestTemplateConfiguration {
64 //...
65 @Bean
66 @ConfigurationProperties("vas-payment-api.oauth2.client")
67 protected ClientCredentialsResourceDetails oAuthDetails() {
68 return new ClientCredentialsResourceDetails();
69 }
70
71 @Bean
72 protected RestTemplate restTemplate() {
73 var restTemplate = new OAuth2RestTemplate(oAuthDetails());
74 restTemplate.setInterceptors(ImmutableList.of(externalRequestInterceptor()));
75 restTemplate.setRequestFactory(httpRequestFactory());
76 return restTemplate;
77 }
78 //...
79 }
80 ```
81 </details>
82
83 <details>
84
85 <summary>HMAC:</summary>
86
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)
89
90 HMAC is implemented using SHA-512 secure hash algorithm.
91
92 Expected `Hmac` header format is:
93
94 ```text
95 HmacSHA512 <user>:<nonce>:<digest>
96 ```
97
98 where `digest` is a Base64 formatted HMAC SHA512 digest of the following string:
99
100 ```text
101 METHOD\n
102 RESOURCE\n
103 USER\
104 NONCE\n
105 DATE\n
106 PAYLOAD\n
107 ```
108
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
111
112 Example request:
113
114 ```bash
115 curl -X POST \
116 https://stage-evc.payex.com/payment-api/api/payments/payment-account/balance \
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 }'
131 ```
132
133 In this example `USER` is user and `SECRET` is secret.
134
135 The plain string to `digest` would then be:
136
137 ```text
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 }
151 ```
152
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.
154
155 Final `Hmac` header value:
156
157 ```text
158 HmacSHA512 user:21a0213e-30eb-85ab-b355-a310d31af30e:oY5Q5Rf1anCz7DRm3GyWR0dvJDnhl/psylfnNCn6FA0NOrQS3L0fvyUsQ1IQ9gQPeLUt9J3IM2zwoSfZpDgRJA==
159 ```
160
161 #### Postman example script
162
163 In pre-request script copy/paste the following snippet:
164
165 ```javascript
166
167 var user = 'user';
168 var secret = 'secret';
169 var transmissionTime = (new Date()).toISOString();
170 var sessionId = guid();
171
172 var hmac = generateHMAC(user, secret, transmissionTime);
173 console.log('hmac: ' + hmac);
174
175 //Set header values
176 pm.request.headers.add({key: 'Hmac', value: hmac });
177 pm.request.headers.add({key: 'Transmission-Time', value: transmissionTime });
178 pm.request.headers.add({key: 'Session-Id', value: sessionId });
179
180 function generateHMAC(user, secret, transmissionTime) {
181
182 var algorithm = "HmacSHA512";
183 var separator = ":";
184 var method = request.method.toUpperCase();
185 var nonce = generateNonce(); //UUID
186 var date = transmissionTime;
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
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'
196 + payload + '\n';
197
198 macData = replaceRequestEnv(macData);
199 console.log('data to mac: ' + macData);
200
201 var hash = CryptoJS.HmacSHA512(macData, secret);
202 var digest = CryptoJS.enc.Base64.stringify(hash);
203 return algorithm + " " + user + separator + nonce + separator + digest;
204 }
205
206 function replaceRequestEnv(input) { //manually set environments to they are populated before hashing
207 return input.replace(/{{([^)]+)}}/g, function (str, key) {
208 var value = pm.environment.get(key);
209 return value === null ? pm.varables.get(key) : value;
210 });
211 }
212
213 function generateNonce() {
214 return guid();
215 }
216
217 function guid() {
218 function s4() {
219 return Math.floor((1 + Math.random()) * 0x10000)
220 .toString(16)
221 .substring(1);
222 }
223
224 return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
225 s4() + '-' + s4() + s4() + s4();
226 }
227
228 ```
229 </details>
230
231 ### Security documentation
232
233 ---
234
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)
239
240 ## First App run
241
242 ---
243
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)).**
246
247 Inside the root directory, do a:
248
249 ```bash
250 mvn clean install
251 ```
252
253 Run the Spring Boot App:
254
255 ```bash
256 mvn --projects backend spring-boot:run
257 ```
258
259 Now go to <http://localhost:8080/> and have a look at your new client.
260
261 ## Testing application
262
263 ---
264
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
268
269 ## Build docker image:
270
271 ---
272
273 ```bash
274 mvn --projects backend clean compile jib:dockerBuild
275 ```
276
277 ## Deploy to local docker:
278
279 ---
280
281 ```bash
282 docker-compose up -d
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: