From empty
To version 7.1
edited by Thomas Warren
on 2020/01/23 13:27
Change comment: There is no comment for this version

Summary

Details

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