From version 2.1
edited by Thomas Warren
on 2020/01/23 13:25
To version 3.1
edited by Thomas Warren
on 2020/01/23 13:25
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
Syntax
... ... @@ -1,1 +1,1 @@
1 -Markdown 1.1
1 +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 +```

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: