diff --git a/src/main/java/com/twikey/DocumentGateway.java b/src/main/java/com/twikey/DocumentGateway.java index 5e4e592..466ee78 100644 --- a/src/main/java/com/twikey/DocumentGateway.java +++ b/src/main/java/com/twikey/DocumentGateway.java @@ -42,7 +42,7 @@ protected DocumentGateway(TwikeyClient twikeyClient) { *
{ + * "id": "11DD32CA20180412220109485", + * "iban": "BE68068097250734", + * "bic": "JVBABE22", + * "amount": 12, + * "msg": "test", + * "place": null, + * "ref": "123", + * "date": "2018-04-12" + * }+ * @throws IOException When no connection could be made + * @throws com.twikey.TwikeyClient.UserException When Twikey returns a user error (400) + */ + public JSONObject create(String customerNumber, Map
{ + * "name": "Beneficiary Name", + * "iban": "BE68068897250734", + * "bic": "JVBABE22", + * "available": true, + * "address": { + * "street": "Veldstraat 11", + * "city": "Gent", + * "zip": "9000", + * "country": "BE" + * } + * }+ * @throws IOException When no connection could be made + * @throws com.twikey.TwikeyClient.UserException When Twikey returns a user error (400) + */ + public JSONObject createBeneficiaryAccount(Customer customer, Account account) throws IOException, TwikeyClient.UserException { + Map
- * String apiKey = "87DA7055C5D18DC5F3FC084F9F208AB335340977"; // found in https://www.twikey.com/r/admin#/c/settings/api - * long ct = 1420; // found @ https://www.twikey.com/r/admin#/c/template + * String apiKey = "87DA7055C5D18DC5F3FC084F9F208AB335340977"; // found in https://www.twikey.com/r/admin#/c/settings/api + * long ct = 1420; // found @ https://www.twikey.com/r/admin#/c/template * TwikeyAPI api = new TwikeyAPI(apiKey); * System.out.println(api.invoice().create(ct,customer,invoiceDetails)); * System.out.println(api.document().create(ct,null,Map.of())); @@ -29,8 +29,6 @@ */ public class TwikeyClient { - private static final String UTF_8 = "UTF-8"; - private static final String DEFAULT_USER_HEADER = "twikey/java-v0.1.0"; private static final String PROD_ENVIRONMENT = "https://api.twikey.com/creditor"; private static final String TEST_ENVIRONMENT = "https://api.beta.twikey.com/creditor"; @@ -50,6 +48,7 @@ public class TwikeyClient { private final InvoiceGateway invoiceGateway; private final TransactionGateway transactionGateway; private final PaylinkGateway paylinkGateway; + private final RefundGateway refundGateway; /** * @param apikey API key @@ -61,6 +60,7 @@ public TwikeyClient(String apikey) { this.invoiceGateway = new InvoiceGateway(this); this.transactionGateway = new TransactionGateway(this); this.paylinkGateway = new PaylinkGateway(this); + this.refundGateway = new RefundGateway(this); } public TwikeyClient withUserAgent(String userAgent) { @@ -119,24 +119,19 @@ protected String getSessionToken() throws IOException, UnauthenticatedException } protected static String getPostDataString(Mapparams) { - try { - StringBuilder result = new StringBuilder(); - boolean first = true; - for (Map.Entry entry : params.entrySet()) { - if (first) - first = false; - else - result.append("&"); - - result.append(URLEncoder.encode(entry.getKey(), UTF_8)); - result.append("="); - result.append(URLEncoder.encode(entry.getValue(), UTF_8)); - } - return result.toString(); - } catch (UnsupportedEncodingException e) { - // should not happen on UTF8 - throw new RuntimeException(e); + StringBuilder result = new StringBuilder(); + boolean first = true; + for (Map.Entry entry : params.entrySet()) { + if (first) + first = false; + else + result.append("&"); + + result.append(URLEncoder.encode(entry.getKey(), UTF_8)); + result.append("="); + result.append(URLEncoder.encode(entry.getValue(), UTF_8)); } + return result.toString(); } public URL getUrl(String path) throws MalformedURLException { @@ -177,6 +172,10 @@ public PaylinkGateway paylink() { return paylinkGateway; } + public RefundGateway refund() { + return refundGateway; + } + public static class UserException extends Throwable { public UserException(String apiError) { super(apiError); @@ -210,9 +209,9 @@ public boolean verifyWebHookSignature(String signatureHeader, String queryString Mac mac; try { mac = Mac.getInstance("HmacSHA256"); - SecretKeySpec secret = new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); + SecretKeySpec secret = new SecretKeySpec(apiKey.getBytes(UTF_8), "HmacSHA256"); mac.init(secret); - byte[] calculated = mac.doFinal(queryString.getBytes(StandardCharsets.UTF_8)); + byte[] calculated = mac.doFinal(queryString.getBytes(UTF_8)); boolean equal = true; for (int i = 0; i < calculated.length; i++) { equal = equal && (providedSignature[i] == calculated[i]); @@ -238,13 +237,13 @@ public static boolean verifyExiturlSignature(String websitekey, String document, Mac mac; try { mac = Mac.getInstance("HmacSHA256"); - SecretKeySpec secret = new SecretKeySpec(websitekey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); + SecretKeySpec secret = new SecretKeySpec(websitekey.getBytes(UTF_8), "HmacSHA256"); mac.init(secret); String payload = document + "/" + status; if (token != null) { payload += "/" + token; } - byte[] calculated = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)); + byte[] calculated = mac.doFinal(payload.getBytes(UTF_8)); boolean equal = true; for (int i = 0; i < calculated.length; i++) { equal = equal && (providedSignature[i] == calculated[i]); @@ -264,11 +263,11 @@ public static boolean verifyExiturlSignature(String websitekey, String document, public static String[] decryptAccountInformation(String websitekey, String document, String encryptedAccount) { String key = document + websitekey; try { - byte[] keyBytes = MessageDigest.getInstance("MD5").digest(key.getBytes(StandardCharsets.UTF_8)); + byte[] keyBytes = MessageDigest.getInstance("MD5").digest(key.getBytes(UTF_8)); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new IvParameterSpec(keyBytes)); byte[] val = cipher.doFinal(hexStringToByteArray(encryptedAccount)); - return new String(val, StandardCharsets.UTF_8).split("/"); + return new String(val, UTF_8).split("/"); } catch (Exception e) { throw new RuntimeException("Exception decrypting : " + encryptedAccount, e); } @@ -285,7 +284,7 @@ private static long generateOtp(String salt, String privateKey) throws GeneralSe byte[] key = hexStringToByteArray(privateKey); if (salt != null) { - byte[] saltBytes = salt.getBytes(StandardCharsets.UTF_8); + byte[] saltBytes = salt.getBytes(UTF_8); byte[] key2 = new byte[saltBytes.length + key.length]; System.arraycopy(saltBytes, 0, key2, 0, saltBytes.length); System.arraycopy(key, 0, key2, saltBytes.length, key.length); diff --git a/src/main/java/com/twikey/callback/RefundCallback.java b/src/main/java/com/twikey/callback/RefundCallback.java new file mode 100644 index 0000000..394ebd6 --- /dev/null +++ b/src/main/java/com/twikey/callback/RefundCallback.java @@ -0,0 +1,23 @@ +package com.twikey.callback; + +import org.json.JSONObject; + +public interface RefundCallback { + + /** + * @param refund Json object containing + * + *
+ */ + void refund(JSONObject refund); +} diff --git a/src/main/java/com/twikey/modal/Account.java b/src/main/java/com/twikey/modal/Account.java new file mode 100644 index 0000000..90aac31 --- /dev/null +++ b/src/main/java/com/twikey/modal/Account.java @@ -0,0 +1,25 @@ +package com.twikey.modal; + +public class Account { + + private final String iban; + private final String bic; + + /** + * @param iban Iban part of the account (mandatory) + * @param bic Bank Identifier, most of the time Twikey will be able to derive the bic from iban, except when specific + * branches need to be targeted in which case it's recommended to add the bic. + */ + public Account(String iban, String bic) { + this.iban = iban; + this.bic = bic; + } + + public String getIban() { + return iban; + } + + public String getBic() { + return bic; + } +} diff --git a/src/main/java/com/twikey/modal/Customer.java b/src/main/java/com/twikey/modal/Customer.java index 805eb40..b92f4bd 100644 --- a/src/main/java/com/twikey/modal/Customer.java +++ b/src/main/java/com/twikey/modal/Customer.java @@ -1,5 +1,8 @@ package com.twikey.modal; +import java.util.HashMap; +import java.util.Map; + public class Customer { private String lastname; @@ -124,4 +127,23 @@ public String getCompanyName() { public String getCoc() { return coc; } + + public Map- id: Twikey id
+ *- iban: IBAN of the beneficiary
+ *- bic: BIC of the beneficiary
+ *- amount: Amount of the refund
+ *- msg: Message for the beneficiary
+ *- place: Optional place
+ *- ref: Your reference
+ *- date: Date when the transfer was requested
+ *- state: Paid
+ *- bkdate: Date when the transfer was done
+ *asFormParameters(){ + Map params = new HashMap<>(); + params.put("customerNumber", getNumber()); + params.put("email", getEmail()); + params.put("firstname", getFirstname()); + params.put("lastname", getLastname()); + params.put("l", getLang()); + params.put("address", getStreet()); + params.put("city", getCity()); + params.put("zip", getZip()); + params.put("country", getCountry()); + params.put("mobile", getMobile()); + if(getCompanyName() != null){ + params.put("companyName", getCompanyName()); + params.put("coc", getCoc()); + } + return params; + } } diff --git a/src/test/java/com/twikey/RefundGatewayTest.java b/src/test/java/com/twikey/RefundGatewayTest.java new file mode 100644 index 0000000..9a5c8ec --- /dev/null +++ b/src/test/java/com/twikey/RefundGatewayTest.java @@ -0,0 +1,67 @@ +package com.twikey; + +import com.twikey.modal.Account; +import com.twikey.modal.Customer; +import org.json.JSONObject; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Map; + +import static org.junit.Assert.*; + +public class RefundGatewayTest { + + private final String apiKey = System.getenv("TWIKEY_API_KEY"); // found in https://www.twikey.com/r/admin#/c/settings/api + + private TwikeyClient api; + + private Customer customer; + + private Account account; + + @Before + public void createCustomer(){ + customer = new Customer() + .setNumber("customerNum123") + .setEmail("no-reply@example.com") + .setFirstname("Twikey") + .setLastname("Support") + .setStreet("Derbystraat 43") + .setCity("Gent") + .setZip("9000") + .setCountry("BE") + .setLang("nl") + .setMobile("32498665995"); + + account = new Account("NL46ABNA8910219718","ABNANL2A"); + + api = new TwikeyClient(apiKey) + .withTestEndpoint() + .withUserAgent("twikey-api-java/junit"); + } + + @Test + public void testCreateBeneficiaryAndRefund() throws IOException, TwikeyClient.UserException { + Assume.assumeTrue("APIKey is set", apiKey != null); + // Add beneficiary account explicitly (if mandates exist for the customer this is optional) + JSONObject beneficiaryResponse = api.refund().createBeneficiaryAccount(customer, account); + assertTrue("Available",beneficiaryResponse.getBoolean("available")); + + JSONObject refundResponse = api.refund().create(customer.getNumber(), Map.of( + "iban", account.getIban(), + "message", "Refund faulty item", + "ref", "My internal reference", + "amount", "10.99" + )); + assertNotNull("Refund id",refundResponse.getString("id")); + + api.refund().feed(refund -> { + assertEquals("Refund was PAID", "PAID", refund.getString("state")); + assertNotNull("Refund has ref", refund.getString("ref")); + assertNotNull("Refund has amount", refund.getBigDecimal("amount")); + }); + } +}