additionalData = new HashMap<>();
36 | private FraudResult fraudResult;
37 | private String pspReference;
38 | private String refusalReason;
39 | private ResultCode resultCode;
40 | private String authCode;
41 | private Amount dccAmount;
42 | private String dccSignature;
43 | private String issuerUrl;
44 | private String md;
45 | private String paRequest;
46 |
47 | @Override
48 | public String toString() {
49 | return new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE)
50 | .append("additionalData", additionalData)
51 | .append("authCode", authCode)
52 | .append("dccAmount", dccAmount)
53 | .append("dccSignature", dccSignature)
54 | .append("fraudResult", fraudResult)
55 | .append("issuerUrl", issuerUrl)
56 | .append("md", md)
57 | .append("paRequest", paRequest)
58 | .append("pspReference", pspReference)
59 | .append("refusalReason", refusalReason)
60 | .append("resultCode", resultCode).toString();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/error/NestedRuntimeException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.error;
18 |
19 | /**
20 | * Handy class for wrapping runtime {@code Exceptions} with a root cause.
21 | *
22 | * This class is {@code abstract} to force the programmer to extend the class. {@code getHttpErrorMessage} will include nested exception
23 | * information; {@code printStackTrace} and other like methods will delegate to the wrapped exception, if any.
24 | *
25 | *
26 | * The similarity between this class and the NestedCheckedException class is unavoidable, as Java forces these two classes to have different superclasses
27 | * (ah, the inflexibility of concrete inheritance!).
28 | *
29 | *
30 | * Note: 'direct inspiration' from Spring/Core. Used here to avoid dependency on spring-core.
31 | *
32 | *
33 | * @author Willian Oki <willian.oki@gmail.com>
34 | */
35 | @SuppressWarnings("serial")
36 | public abstract class NestedRuntimeException extends RuntimeException {
37 | /**
38 | * Construct a {@code NestedRuntimeException} with the specified detail message.
39 | *
40 | * @param msg the detail message
41 | */
42 | public NestedRuntimeException(final String msg) {
43 | super(msg);
44 | }
45 |
46 | /**
47 | * Construct a {@code NestedRuntimeException} with the specified detail message and nested exception.
48 | *
49 | * @param msg the detail message
50 | * @param cause the nested exception
51 | */
52 | public NestedRuntimeException(final String msg, final Throwable cause) {
53 | super(msg, cause);
54 | }
55 |
56 | /**
57 | * Return the detail message, including the message from the nested exception if there is one.
58 | */
59 | @Override
60 | public String getMessage() {
61 | return NestedExceptionUtils.buildMessage(super.getMessage(), getCause());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/java/com/github/woki/payments/adyen/action/ActionUtilEncryptionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.action;
18 |
19 | import org.apache.commons.lang3.StringUtils;
20 | import org.junit.Assert;
21 | import org.junit.Test;
22 |
23 | import java.security.InvalidKeyException;
24 |
25 | /**
26 | * @author Willian Oki <willian.oki@gmail.com>
27 | */
28 | public class ActionUtilEncryptionTest {
29 | private static final String PUBKEY_TEXT = "10001|9B27E6AE115FB582C795C40F19BF77C2DD6875C4E410E39AFD3A861408A3D9A97057AFFC8D7C3FE3B3314ACEC2F8C3036CB6D6212005107529E253218240DC95173E45B9856C6266BBBB05797400674C028E1F86134F6BBE752C47ADD1A35BEAB972E8F2EFBCB9057C70EDE365BF9E8C9B75E58C2ED4AE34DFF2FAFB5DC1886AE1D90B13D48F6182CA0E37881D9277AED7F745D544EBDB066E129D2B74F5B294526679956E9CFA9A83624C67621796F95CB011B133A7D2DC18934D7505A31E5EABB7E05E21FFD33F9885A30BD494ED0F174FA0630BB1A60F270E30B8F8BCC18C0FA085D938AE12D7EC2D64254615F602A07D229517F46DC31CF354B7E6E12783";
30 | private static final String PUBKEY_TEXT_ERR1 = "foo";
31 | private static final String PUBKEY_TEXT_ERR2 = "1|2";
32 |
33 | @Test
34 | public void testEncryption() throws Exception {
35 | String result = CSEUtil.encrypt(CSEUtil.aesCipher(), CSEUtil.rsaCipher(PUBKEY_TEXT), "4444444444444444");
36 | System.out.println(result);
37 | Assert.assertTrue(StringUtils.isNotBlank(result));
38 | }
39 |
40 | @Test(expected = InvalidKeyException.class)
41 | public void testEncryptionError1() throws Exception {
42 | String result = CSEUtil.encrypt(CSEUtil.aesCipher(), CSEUtil.rsaCipher(PUBKEY_TEXT_ERR1), "4444444444444444");
43 | System.out.println(result);
44 | }
45 |
46 | @Test(expected = IllegalArgumentException.class)
47 | public void testEncryptionError2() throws Exception {
48 | String result = CSEUtil.encrypt(CSEUtil.aesCipher(), CSEUtil.rsaCipher(PUBKEY_TEXT_ERR2), "4444444444444444");
49 | System.out.println(result);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/model/Card.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.model;
18 |
19 | import com.github.woki.payments.adyen.support.ToStringStyle;
20 | import lombok.Getter;
21 | import lombok.Setter;
22 | import org.apache.commons.lang3.builder.ToStringBuilder;
23 |
24 | import java.io.Serializable;
25 | import java.text.SimpleDateFormat;
26 | import java.util.Date;
27 | import java.util.TimeZone;
28 |
29 | /**
30 | * @author Willian Oki <willian.oki@gmail.com>
31 | */
32 | @SuppressWarnings("serial")
33 | @Getter
34 | @Setter
35 | public class Card implements Serializable {
36 | private Integer expiryMonth;
37 | private Integer expiryYear;
38 | private String holderName;
39 | private String number;
40 | private String cvc;
41 | private String generationtime;
42 | private Integer issueNumber;
43 | private Integer startMonth;
44 | private Integer startYear;
45 |
46 | private static final SimpleDateFormat GENERATION_TIME_FORMAT;
47 |
48 | static {
49 | GENERATION_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
50 | GENERATION_TIME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
51 | }
52 |
53 | public static final String CARD_ENCRYPTED_ADDITIONAL_DATA_KEY_NAME = "card.encrypted.json";
54 |
55 | public void setGenerationtime(Date generationtime) {
56 | if (generationtime != null) {
57 | this.generationtime = GENERATION_TIME_FORMAT.format(generationtime);
58 | }
59 | }
60 |
61 | @Override
62 | public String toString() {
63 | return new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE)
64 | .append("expiryMonth", expiryMonth)
65 | .append("expiryYear", expiryYear)
66 | .append("holderName", holderName)
67 | .append("cardNumber", number)
68 | .append("cvc", cvc)
69 | .append("issueNumber", issueNumber)
70 | .append("startMonth", startMonth)
71 | .append("startYear", startYear)
72 | .toString();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/model/ThreeDSecureDataBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.model;
18 |
19 | /**
20 | * @author Willian Oki <willian.oki@gmail.com>
21 | */
22 | public final class ThreeDSecureDataBuilder {
23 | private ThreeDSecureDataBuilder() {
24 | // builder
25 | }
26 |
27 | public static ICavv authenticationResponse(String response) {
28 | return new Builder(response);
29 | }
30 |
31 | public interface ICavv {
32 | IDirResponse cavv(String cavv, String algorithm);
33 | }
34 |
35 | public interface IDirResponse {
36 | IEci directoryResponse(String response);
37 | }
38 |
39 | public interface IEci {
40 | IXid eci(String eci);
41 | }
42 |
43 | public interface IXid {
44 | IBuilder xid(String xid);
45 | }
46 |
47 | public interface IBuilder {
48 | ThreeDSecureData build();
49 | }
50 |
51 | private static final class Builder implements IBuilder, ICavv, IDirResponse, IEci, IXid {
52 | private ThreeDSecureData data;
53 |
54 | Builder(String authenticationResponse) {
55 | data = new ThreeDSecureData();
56 | data.setAuthenticationResponse(authenticationResponse);
57 | }
58 |
59 | @Override
60 | public ThreeDSecureData build() {
61 | return data;
62 | }
63 |
64 | @Override
65 | public IDirResponse cavv(String cavv, String algorithm) {
66 | data.setCavv(cavv);
67 | data.setCavvAlgorithm(algorithm);
68 | return this;
69 | }
70 |
71 | @Override
72 | public IEci directoryResponse(String response) {
73 | data.setDirectoryResponse(response);
74 | return this;
75 | }
76 |
77 | @Override
78 | public IXid eci(String eci) {
79 | data.setEci(eci);
80 | return this;
81 | }
82 |
83 | @Override
84 | public IBuilder xid(String xid) {
85 | data.setXid(xid);
86 | return this;
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/support/APService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.support;
18 |
19 | import com.github.woki.payments.adyen.action.Options;
20 | import com.github.woki.payments.adyen.model.ModificationRequest;
21 | import com.github.woki.payments.adyen.model.PaymentRequest;
22 | import com.github.woki.payments.adyen.model.RecurringDisableRequest;
23 | import com.github.woki.payments.adyen.model.RecurringListDetailsRequest;
24 |
25 | /**
26 | * @author Willian Oki <willian.oki@gmail.com>
27 | */
28 | public enum APService {
29 | AUTHORISATION("/pal/servlet/Payment/v30/authorise"),
30 | AUTHORISATION_3D("/pal/servlet/Payment/v30/authorise3d"),
31 | CAPTURE("/pal/servlet/Payment/v30/capture"),
32 | REFUND("/pal/servlet/Payment/v30/refund"),
33 | CANCEL("/pal/servlet/Payment/v30/cancel"),
34 | CANCEL_OR_REFUND("/pal/servlet/Payment/v30/cancelOrRefund"),
35 | RECURRING_DISABLE("/pal/servlet/Recurring/v30/disable"),
36 | RECURRING_LIST_DETAILS("/pal/servlet/Recurring/v30/listRecurringDetails")
37 | ;
38 |
39 | final String path;
40 |
41 | APService(String path) {
42 | this.path = path;
43 | }
44 |
45 | public String getPath() {
46 | return path;
47 | }
48 |
49 | public static APService from(final ReqType request, final Options opts) {
50 | if (request instanceof PaymentRequest) {
51 | if (opts != null && opts.has("threeds")) {
52 | return AUTHORISATION_3D;
53 | }
54 | return AUTHORISATION;
55 | }
56 | if (request instanceof ModificationRequest) {
57 | if ("capture".equals(opts.getValue("action"))) {
58 | return CAPTURE;
59 | }
60 | if ("refund".equals(opts.getValue("action"))) {
61 | return REFUND;
62 | }
63 | if ("cancel".equals(opts.getValue("action"))) {
64 | return CANCEL;
65 | }
66 | if ("cancelOrRefund".equals(opts.getValue("action"))) {
67 | return CANCEL_OR_REFUND;
68 | }
69 | }
70 | if (request instanceof RecurringDisableRequest) {
71 | return RECURRING_DISABLE;
72 | }
73 | if (request instanceof RecurringListDetailsRequest) {
74 | return RECURRING_LIST_DETAILS;
75 | }
76 | return null;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/model/AddressBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.model;
18 |
19 | import com.neovisionaries.i18n.CountryCode;
20 |
21 | /**
22 | * @author Willian Oki <willian.oki@gmail.com>
23 | */
24 | public final class AddressBuilder {
25 | private AddressBuilder() {
26 | // utility
27 | }
28 |
29 | public static INumber street(String street) {
30 | return new Builder(street);
31 | }
32 |
33 | public interface INumber {
34 | IPostalCode numberOrName(String number);
35 | }
36 |
37 | public interface IPostalCode {
38 | ICity postalCode(String postalCode);
39 | }
40 |
41 | public interface ICity {
42 | IState city(String city);
43 | }
44 |
45 | public interface IState {
46 | ICountry state(String state);
47 | }
48 |
49 | public interface ICountry {
50 | IBuilder country(CountryCode country);
51 | }
52 |
53 | public interface IBuilder {
54 | Address build();
55 | }
56 |
57 | private static final class Builder implements IBuilder, INumber, IPostalCode, ICity, IState, ICountry {
58 | private Address address;
59 |
60 | Builder(String street) {
61 | address = new Address();
62 | address.setStreet(street);
63 | }
64 |
65 | @Override
66 | public Address build() {
67 | return address;
68 | }
69 |
70 | @Override
71 | public IBuilder country(CountryCode country) {
72 | address.setCountry(country);
73 | return this;
74 | }
75 |
76 | @Override
77 | public ICountry state(String state) {
78 | address.setStateOrProvince(state);
79 | return this;
80 | }
81 |
82 | @Override
83 | public IState city(String city) {
84 | address.setCity(city);
85 | return this;
86 | }
87 |
88 | @Override
89 | public IPostalCode numberOrName(String number) {
90 | address.setHouseNumberOrName(number);
91 | return this;
92 | }
93 |
94 | @Override
95 | public ICity postalCode(String postalCode) {
96 | address.setPostalCode(postalCode);
97 | return this;
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/model/RecurringDetail.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.model;
18 |
19 | import lombok.Getter;
20 | import lombok.Setter;
21 | import org.apache.commons.lang3.builder.ToStringBuilder;
22 | import org.apache.commons.lang3.builder.ToStringStyle;
23 |
24 | import java.io.Serializable;
25 | import java.util.Date;
26 | import java.util.List;
27 | import java.util.Map;
28 |
29 | /**
30 | * @author Willian Oki <willian.oki@gmail.com>
31 | */
32 | @SuppressWarnings("serial")
33 | @Getter
34 | @Setter
35 | public class RecurringDetail implements Serializable {
36 | private String recurringDetailReference;
37 | private String name;
38 | private String variant;
39 | private String paymentMethodVariant;
40 | private Card card;
41 | private ELV elv;
42 | private BankAccount bank;
43 | private TokenDetails tokenDetails;
44 | private Name shopperName;
45 | private String socialSecurityNumber;
46 | private Address billingAddress;
47 | private String alias;
48 | private String aliasType;
49 | private String firstPspReference;
50 | private Date creationDate;
51 | private Map additionalData;
52 | private List contractTypes;
53 | private String acquirer;
54 | private String acquirerAccount;
55 |
56 | @Override
57 | public String toString() {
58 | return new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE)
59 | .append("recurringDetailReference", recurringDetailReference)
60 | .append("name", name)
61 | .append("variant", variant)
62 | .append("paymentMethodVariant", paymentMethodVariant)
63 | .append("card", card)
64 | .append("elv", elv)
65 | .append("bank", bank)
66 | .append("tokenDetails", tokenDetails)
67 | .append("shopperName", shopperName)
68 | .append("socialSecurityNumber", socialSecurityNumber)
69 | .append("billingAddress", billingAddress)
70 | .append("alias", alias)
71 | .append("aliasType", aliasType)
72 | .append("firstPspReference", firstPspReference)
73 | .append("creationDate", creationDate)
74 | .append("additionalData", additionalData)
75 | .append("contractTypes", contractTypes)
76 | .append("acquirer", acquirer)
77 | .append("acquirerAccount", acquirerAccount).toString();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/model/CardBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.model;
18 |
19 | /**
20 | * @author Willian Oki <willian.oki@gmail.com>
21 | */
22 | public final class CardBuilder {
23 | private CardBuilder() {
24 | // utility
25 | }
26 |
27 | public static ICvc number(String number) {
28 | return new Builder(number);
29 | }
30 |
31 | public interface ICvc {
32 | IExpiry cvc(String cvc);
33 | }
34 |
35 | public interface IExpiry {
36 | IHolder expiry(Integer year, Integer month);
37 | }
38 |
39 | public interface IHolder {
40 | IBuilder holder(String holder);
41 | }
42 |
43 | public interface IBuilder {
44 | IBuilder issueNumber(Integer issueNumber);
45 |
46 | IBuilder startMonth(Integer startMonth);
47 |
48 | IBuilder startYear(Integer startYear);
49 |
50 | Card build();
51 | }
52 |
53 | private static final class Builder implements IBuilder, ICvc, IExpiry, IHolder {
54 | private Card card;
55 |
56 | Builder(String number) {
57 | card = new Card();
58 | card.setNumber(number);
59 | }
60 |
61 | @Override
62 | public IBuilder holder(String holder) {
63 | card.setHolderName(holder);
64 | return this;
65 | }
66 |
67 | @Override
68 | public IHolder expiry(Integer year, Integer month) {
69 | card.setExpiryMonth(month);
70 | card.setExpiryYear(year);
71 | return this;
72 | }
73 |
74 | @Override
75 | public IExpiry cvc(String cvc) {
76 | card.setCvc(cvc);
77 | return this;
78 | }
79 |
80 | @Override
81 | public IBuilder issueNumber(Integer issueNumber) {
82 | card.setIssueNumber(issueNumber);
83 | return this;
84 | }
85 |
86 | @Override
87 | public IBuilder startMonth(Integer startMonth) {
88 | card.setStartMonth(startMonth);
89 | return this;
90 | }
91 |
92 | @Override
93 | public IBuilder startYear(Integer startYear) {
94 | card.setStartYear(startYear);
95 | return this;
96 | }
97 |
98 | @Override
99 | public Card build() {
100 | return card;
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/support/ToStringStyle.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.support;
18 |
19 | /**
20 | * @author Willian Oki <willian.oki@gmail.com>
21 | */
22 | public class ToStringStyle extends org.apache.commons.lang3.builder.ToStringStyle {
23 | private static final int CARD_MIN_LEN = 14;
24 | private static final int CARD_BIN_LEN = 6;
25 | private static final int CARD_SUFFIX_LEN = 4;
26 | private static final int CARD_UNMASKED_LEN = CARD_BIN_LEN + CARD_SUFFIX_LEN;
27 | private static final char CARD_MASK_CHAR = '*';
28 |
29 | public static final ToStringStyle DEFAULT_STYLE = new ToStringStyle();
30 |
31 | public ToStringStyle() {
32 | super();
33 | setUseShortClassName(true);
34 | setUseIdentityHashCode(false);
35 | }
36 |
37 | @Override
38 | protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
39 | switch (fieldName) {
40 | case "cardNumber":
41 | buffer.append(maskCardNumber((String) value));
42 | break;
43 | case "cvc":
44 | String original = (String) value;
45 | buffer.append(extractMask(original.length(), 0, CARD_MASK_CHAR));
46 | break;
47 | default:
48 | buffer.append(value);
49 | }
50 | }
51 |
52 | private static String maskCardNumber(final String number) {
53 | if (number == null || number.length() < CARD_MIN_LEN) {
54 | return "";
55 | }
56 | return extractCardBin(number) + extractMask(number.length(), CARD_UNMASKED_LEN, CARD_MASK_CHAR) + extractCardSuffix(number);
57 | }
58 |
59 | private static String extractCardBin(final String number) {
60 | if (number != null && number.length() >= CARD_MIN_LEN) {
61 | return number.substring(0, CARD_BIN_LEN);
62 | }
63 | return "";
64 | }
65 |
66 | private static String extractCardSuffix(final String number) {
67 | if (number != null && number.length() >= CARD_MIN_LEN) {
68 | return number.substring(number.length() - CARD_SUFFIX_LEN);
69 | }
70 | return "";
71 | }
72 |
73 | private static String extractMask(int originalLength, int unmaskedLength, char mask) {
74 | int maskLength = originalLength - unmaskedLength;
75 | if (maskLength <= 0) {
76 | return "";
77 | }
78 |
79 | char[] buff = new char[maskLength];
80 | while (maskLength > 0) {
81 | maskLength--;
82 | buff[maskLength] = mask;
83 | }
84 | return new String(buff);
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/test/java/com/github/woki/payments/adyen/simulator/web/controller/PaymentController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.simulator.web.controller;
18 |
19 | import com.github.woki.payments.adyen.model.PaymentRequest;
20 | import com.github.woki.payments.adyen.model.PaymentResponse;
21 | import org.springframework.http.HttpStatus;
22 | import org.springframework.http.ResponseEntity;
23 | import org.springframework.web.bind.annotation.RequestBody;
24 | import org.springframework.web.bind.annotation.RequestMapping;
25 | import org.springframework.web.bind.annotation.RequestMethod;
26 | import org.springframework.web.bind.annotation.RestController;
27 |
28 | /**
29 | * @author Willian Oki <willian.oki@gmail.com>
30 | */
31 | @RestController
32 | public class PaymentController {
33 | @RequestMapping(value = {"/pal/servlet/Payment/v30/authorise", "/pal/servlet/Payment/v30/authorise3d"}, method = RequestMethod.POST)
34 | public ResponseEntity authorize(@RequestBody PaymentRequest request) {
35 | PaymentResponse res = new PaymentResponse();
36 | if ("gimme_500".equals(request.getReference())) {
37 | res.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
38 | return new ResponseEntity<>(res, HttpStatus.INTERNAL_SERVER_ERROR);
39 | }
40 | if ("gimme_400".equals(request.getReference())) {
41 | res.setStatus(HttpStatus.BAD_REQUEST.value());
42 | return new ResponseEntity<>(res, HttpStatus.BAD_REQUEST);
43 | }
44 | if ("gimme_422".equals(request.getReference())) {
45 | res.setStatus(HttpStatus.UNPROCESSABLE_ENTITY.value());
46 | return new ResponseEntity<>(res, HttpStatus.UNPROCESSABLE_ENTITY);
47 | }
48 | if ("gimme_401".equals(request.getReference())) {
49 | res.setStatus(HttpStatus.UNAUTHORIZED.value());
50 | return new ResponseEntity<>(res, HttpStatus.UNAUTHORIZED);
51 | }
52 | if ("gimme_403".equals(request.getReference())) {
53 | res.setStatus(HttpStatus.FORBIDDEN.value());
54 | return new ResponseEntity<>(res, HttpStatus.FORBIDDEN);
55 | }
56 | if ("gimme_404".equals(request.getReference())) {
57 | res.setStatus(HttpStatus.NOT_FOUND.value());
58 | return new ResponseEntity<>(res, HttpStatus.NOT_FOUND);
59 | }
60 | if ("gimme_200".equals(request.getReference())) {
61 | res.setStatus(HttpStatus.OK.value());
62 | return new ResponseEntity<>(res, HttpStatus.OK);
63 | }
64 | res.setStatus(HttpStatus.NON_AUTHORITATIVE_INFORMATION.value());
65 | return new ResponseEntity<>(res, HttpStatus.NON_AUTHORITATIVE_INFORMATION);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/IClient.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen;
18 |
19 | import com.github.woki.payments.adyen.error.APSAccessException;
20 | import com.github.woki.payments.adyen.model.*;
21 |
22 | /**
23 | * @author Willian Oki <willian.oki@gmail.com>
24 | */
25 | public interface IClient {
26 | /**
27 | * @return the {@link ClientConfig}
28 | */
29 | ClientConfig getClientConfig();
30 | /**
31 | * @param request the request
32 | *
33 | * @return the response
34 | *
35 | * @throws APSAccessException on communication error
36 | */
37 | PaymentResponse authorise(final PaymentRequest request);
38 | /**
39 | * @param request the request
40 | *
41 | * @return the response
42 | *
43 | * @throws APSAccessException on communication error
44 | */
45 | PaymentResponse authorise3ds(final PaymentRequest request);
46 | /**
47 | * @param request the request
48 | *
49 | * @return the response
50 | *
51 | * @throws APSAccessException on communication error
52 | */
53 | PaymentResponse verifyBin(final PaymentRequest request);
54 | /**
55 | * @param request the request
56 | *
57 | * @return the response
58 | *
59 | * @throws APSAccessException on communication error
60 | */
61 | ModificationResponse capture(final ModificationRequest request);
62 | /**
63 | * @param request the request
64 | *
65 | * @return the response
66 | *
67 | * @throws APSAccessException on communication error
68 | */
69 | ModificationResponse cancel(final ModificationRequest request);
70 | /**
71 | * @param request the request
72 | *
73 | * @return the response
74 | *
75 | * @throws APSAccessException on communication error
76 | */
77 | ModificationResponse refund(final ModificationRequest request);
78 | /**
79 | * @param request the request
80 | *
81 | * @return the response
82 | *
83 | * @throws APSAccessException on communication error
84 | */
85 | ModificationResponse cancelOrRefund(final ModificationRequest request);
86 | /**
87 | * @param request the request
88 | *
89 | * @return the response
90 | *
91 | * @throws APSAccessException on communication error
92 | */
93 | RecurringDisableResponse recurringDisable(final RecurringDisableRequest request);
94 | /**
95 | * @param request the request
96 | *
97 | * @return the response
98 | *
99 | * @throws APSAccessException on communication error
100 | */
101 | RecurringListDetailsResponse recurringListDetails(final RecurringListDetailsRequest request);
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/model/ForexQuoteBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.model;
18 |
19 | import java.util.Date;
20 |
21 | /**
22 | * @author Willian Oki <willian.oki@gmail.com>
23 | */
24 | public final class ForexQuoteBuilder {
25 | private ForexQuoteBuilder() {
26 | // builder
27 | }
28 |
29 | public static IValidTill base(String type, String reference, Integer basePoints) {
30 | return new Builder(type, reference, basePoints);
31 | }
32 |
33 | public interface IValidTill {
34 | IAccount validTill(Date date);
35 | }
36 |
37 | public interface IAccount {
38 | IAmounts account(String account, String accountType);
39 | }
40 |
41 | public interface IAmounts {
42 | ISource amounts(Amount base, Amount interbank, Amount buy, Amount sell);
43 | }
44 |
45 | public interface ISource {
46 | ISignature source(String source);
47 | }
48 |
49 | public interface ISignature {
50 | IBuilder signature(String signature);
51 | }
52 |
53 | public interface IBuilder {
54 | ForexQuote build();
55 | }
56 |
57 | private static final class Builder implements IBuilder, IValidTill, IAccount, IAmounts, ISource, ISignature {
58 | private ForexQuote quote;
59 |
60 | Builder(String type, String reference, Integer basePoints) {
61 | quote = new ForexQuote();
62 | quote.setType(type);
63 | quote.setReference(reference);
64 | quote.setBasePoints(basePoints);
65 | }
66 |
67 | @Override
68 | public IAmounts account(String account, String accountType) {
69 | quote.setAccount(account);
70 | quote.setAccountType(accountType);
71 | return this;
72 | }
73 |
74 | @Override
75 | public ISource amounts(Amount base, Amount interbank, Amount buy, Amount sell) {
76 | quote.setBaseAmount(base);
77 | quote.setInterbank(interbank);
78 | quote.setBuy(buy);
79 | quote.setSell(sell);
80 | return this;
81 | }
82 |
83 | @Override
84 | public ForexQuote build() {
85 | return quote;
86 | }
87 |
88 | @Override
89 | public IBuilder signature(String signature) {
90 | quote.setSignature(signature);
91 | return this;
92 | }
93 |
94 | @Override
95 | public ISignature source(String source) {
96 | quote.setSource(source);
97 | return this;
98 | }
99 |
100 | @Override
101 | public IAccount validTill(Date date) {
102 | quote.setValidTill(date);
103 | return this;
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/model/ModificationRequestBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.model;
18 |
19 | /**
20 | * @author Willian Oki <willian.oki@gmail.com>
21 | */
22 | public final class ModificationRequestBuilder {
23 | private ModificationRequestBuilder() {
24 | // utility
25 | }
26 |
27 | public static IOriginalReference merchantAccount(String account) {
28 | return new Builder(account);
29 | }
30 |
31 | public interface IOriginalReference {
32 | IBuilder originalReference(String reference);
33 | }
34 |
35 | public interface IBuilder {
36 | IBuilder additionalData(String key, String value);
37 | IBuilder authorisationCode(String code);
38 | IBuilder reference(String reference);
39 | IBuilder modificationAmount(Amount amount);
40 | IBuilder tenderReference(String tenderReference);
41 | IBuilder uniqueTerminalId(String uniqueTerminalId);
42 | ModificationRequest build();
43 | }
44 |
45 | private static final class Builder implements IOriginalReference, IBuilder {
46 | private ModificationRequest request;
47 |
48 | Builder(String merchantAccount) {
49 | request = new ModificationRequest();
50 | request.setMerchantAccount(merchantAccount);
51 | }
52 |
53 | @Override
54 | public IBuilder reference(String reference) {
55 | request.setReference(reference);
56 | return this;
57 | }
58 |
59 | @Override
60 | public ModificationRequest build() {
61 | return request;
62 | }
63 |
64 | @Override
65 | public IBuilder originalReference(String reference) {
66 | request.setOriginalReference(reference);
67 | return this;
68 | }
69 |
70 | @Override
71 | public IBuilder modificationAmount(Amount amount) {
72 | request.setModificationAmount(amount);
73 | return this;
74 | }
75 |
76 | @Override
77 | public IBuilder tenderReference(String tenderReference) {
78 | request.setTenderReference(tenderReference);
79 | return this;
80 | }
81 |
82 | @Override
83 | public IBuilder uniqueTerminalId(String uniqueTerminalId) {
84 | request.setUniqueTerminalId(uniqueTerminalId);
85 | return this;
86 | }
87 |
88 | @Override
89 | public IBuilder authorisationCode(String code) {
90 | request.setAuthorisationCode(code);
91 | return this;
92 | }
93 |
94 | @Override
95 | public IBuilder additionalData(String key, String value) {
96 | request.getAdditionalData().put(key, value);
97 | return this;
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/model/BankAccountBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.model;
18 |
19 | /**
20 | * @author Willian Oki <willian.oki@gmail.com>
21 | */
22 | public final class BankAccountBuilder {
23 | private BankAccountBuilder() {
24 | // utility
25 | }
26 |
27 | public static ILocationId accountNumber(String accountNumber) {
28 | return new Builder(accountNumber);
29 | }
30 |
31 | public interface ILocationId {
32 | IBankName locationId(String locationId);
33 | }
34 |
35 | public interface IBankName {
36 | IBankCity bankName(String name);
37 | }
38 |
39 | public interface IBankCity {
40 | IBic bankCity(String city);
41 | }
42 |
43 | public interface IBic {
44 | ICountryCode bic(String bic);
45 | }
46 |
47 | public interface ICountryCode {
48 | IIban countryCode(String countryCode);
49 | }
50 |
51 | public interface IIban {
52 | IOwnerName iban(String iban);
53 | }
54 |
55 | public interface IOwnerName {
56 | ITaxId owner(String owner);
57 | }
58 |
59 | public interface ITaxId {
60 | IBuilder taxId(String taxId);
61 | }
62 |
63 | public interface IBuilder {
64 | BankAccount build();
65 | }
66 |
67 | private static final class Builder implements IBuilder, ILocationId, IBankName, IBankCity, IBic, ICountryCode, IIban, IOwnerName, ITaxId {
68 | private BankAccount account;
69 |
70 | Builder(String accountNumber) {
71 | account = new BankAccount();
72 | account.setBankAccountNumber(accountNumber);
73 | }
74 |
75 | @Override
76 | public IBankCity bankName(String name) {
77 | account.setBankName(name);
78 | return this;
79 | }
80 |
81 | @Override
82 | public ICountryCode bic(String bic) {
83 | account.setBic(bic);
84 | return this;
85 | }
86 |
87 | @Override
88 | public BankAccount build() {
89 | return account;
90 | }
91 |
92 | @Override
93 | public IIban countryCode(String countryCode) {
94 | account.setCountryCode(countryCode);
95 | return this;
96 | }
97 |
98 | @Override
99 | public IOwnerName iban(String iban) {
100 | account.setIban(iban);
101 | return this;
102 | }
103 |
104 | @Override
105 | public IBankName locationId(String locationId) {
106 | account.setBankLocationId(locationId);
107 | return this;
108 | }
109 |
110 | @Override
111 | public ITaxId owner(String owner) {
112 | account.setOwnerName(owner);
113 | return this;
114 | }
115 |
116 | @Override
117 | public IBic bankCity(String city) {
118 | account.setBankCity(city);
119 | return this;
120 | }
121 |
122 | @Override
123 | public IBuilder taxId(String taxId) {
124 | account.setTaxId(taxId);
125 | return this;
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/test/java/com/github/woki/payments/adyen/ClientConfigTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen;
18 |
19 | import com.github.woki.payments.adyen.support.APService;
20 | import com.github.woki.payments.adyen.support.APUtil;
21 | import org.apache.http.HttpHost;
22 | import org.junit.Assert;
23 | import org.junit.Test;
24 |
25 | /**
26 | * @author Willian Oki <willian.oki@gmail.com>
27 | */
28 | public class ClientConfigTest {
29 | private static final String PROXY_CONFIG = "prxyusr:prxypass@prxyhost:8888";
30 | private static final String PROXY_CONFIG_2 = "62.63.4.10:8888";
31 |
32 | @Test
33 | public void testClientConfigCreation() throws Exception {
34 | ClientConfig config = new ClientConfig(APUtil.TEST_ENDPOINT);
35 | Assert.assertTrue(config.getEndpointHost().equals(HttpHost.create(APUtil.TEST_ENDPOINT)));
36 | Assert.assertTrue(config.getEndpointPort(APService.AUTHORISATION).equals(APUtil.TEST_ENDPOINT + APService.AUTHORISATION.getPath()));
37 | Assert.assertTrue(config.getEndpointPort(APService.AUTHORISATION_3D).equals(APUtil.TEST_ENDPOINT + APService.AUTHORISATION_3D.getPath()));
38 | Assert.assertTrue(config.getEndpointPort(APService.CANCEL).equals(APUtil.TEST_ENDPOINT + APService.CANCEL.getPath()));
39 | Assert.assertTrue(config.getEndpointPort(APService.CANCEL_OR_REFUND).equals(APUtil.TEST_ENDPOINT + APService.CANCEL_OR_REFUND.getPath()));
40 | Assert.assertTrue(config.getEndpointPort(APService.CAPTURE).equals(APUtil.TEST_ENDPOINT + APService.CAPTURE.getPath()));
41 | Assert.assertTrue(config.getEndpointPort(APService.REFUND).equals(APUtil.TEST_ENDPOINT + APService.REFUND.getPath()));
42 | Assert.assertTrue(config.getConnectionTimeout() == 0);
43 | Assert.assertTrue(config.getSocketTimeout() == 0);
44 | Assert.assertFalse(config.hasProxy());
45 | }
46 |
47 | @Test(expected = IllegalArgumentException.class)
48 | public void testClientConfigCreationFailure1() throws Exception {
49 | ClientConfig config = new ClientConfig("");
50 | }
51 |
52 | @Test(expected = IllegalArgumentException.class)
53 | public void testClientConfigCreationFailure2() throws Exception {
54 | ClientConfig config = new ClientConfig(null);
55 | }
56 |
57 | @Test(expected = IllegalArgumentException.class)
58 | public void testClientConfigCreationFailure3() throws Exception {
59 | ClientConfig config = new ClientConfig("blah-blah");
60 | }
61 |
62 | @Test
63 | public void testClientConfigProxyStuff() throws Exception {
64 | ClientConfig config = new ClientConfig(APUtil.TEST_ENDPOINT);
65 | config.setProxyConfig(PROXY_CONFIG);
66 | Assert.assertTrue(config.hasProxy());
67 | Assert.assertTrue(config.isProxyAuthenticated());
68 | Assert.assertTrue(config.getProxyUsername().equals("prxyusr"));
69 | Assert.assertTrue(config.getProxyPassword().equals("prxypass"));
70 | Assert.assertTrue(config.getProxyHost().equals(HttpHost.create("prxyhost:8888")));
71 | }
72 |
73 | @Test
74 | public void testClientConfigProxyStuff2() throws Exception {
75 | ClientConfig config = new ClientConfig(APUtil.TEST_ENDPOINT);
76 | config.setProxyConfig(PROXY_CONFIG_2);
77 | Assert.assertTrue(config.hasProxy());
78 | Assert.assertFalse(config.isProxyAuthenticated());
79 | Assert.assertTrue(config.getProxyUsername() == null);
80 | Assert.assertTrue(config.getProxyPassword() == null);
81 | Assert.assertTrue(config.getProxyHost().equals(HttpHost.create(PROXY_CONFIG_2)));
82 | }
83 |
84 | @Test
85 | public void testToString() {
86 | ClientConfig config = new ClientConfig(APUtil.TEST_ENDPOINT);
87 | config.setProxyConfig(PROXY_CONFIG);
88 | System.out.println(config);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/action/CSEUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.action;
18 |
19 | import org.apache.commons.codec.binary.Base64;
20 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
21 |
22 | import javax.crypto.*;
23 | import javax.crypto.spec.IvParameterSpec;
24 | import java.math.BigInteger;
25 | import java.security.*;
26 | import java.security.spec.InvalidKeySpecException;
27 | import java.security.spec.RSAPublicKeySpec;
28 | import java.util.Locale;
29 |
30 | /**
31 | * @author Willian Oki <willian.oki@gmail.com>
32 | */
33 | public final class CSEUtil {
34 | private static final String CSE_VERSION = "1_0_0";
35 | private static final String CSE_SEPARATOR = "$";
36 | private static final String CSE_PREFIX = "payments-adyen-api_";
37 | private static final SecureRandom CSE_RANDOM = new SecureRandom();
38 |
39 | static {
40 | Security.addProvider(new BouncyCastleProvider());
41 | }
42 |
43 | private CSEUtil() {
44 | // utility class
45 | }
46 |
47 | private static SecretKey aesKey(int keySize) throws NoSuchAlgorithmException {
48 | KeyGenerator kgen = KeyGenerator.getInstance("AES");
49 | kgen.init(keySize);
50 | return kgen.generateKey();
51 | }
52 |
53 | private static synchronized byte[] iv(SecureRandom random, int ivSize) {
54 | byte[] iv = new byte[ivSize];
55 | random.nextBytes(iv);
56 | return iv;
57 | }
58 |
59 | static String encrypt(final Cipher aesCipher, final Cipher rsaCipher, final String plainText) throws BadPaddingException, IllegalBlockSizeException,
60 | NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
61 | SecretKey aesKey = aesKey(256);
62 | byte[] iv = iv(CSE_RANDOM, 12);
63 | aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(iv));
64 | byte[] encrypted = aesCipher.doFinal(plainText.getBytes());
65 |
66 | byte[] result = new byte[iv.length + encrypted.length];
67 | System.arraycopy(iv, 0, result, 0, iv.length);
68 | System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
69 |
70 | byte[] encryptedAESKey;
71 | try {
72 | encryptedAESKey = rsaCipher.doFinal(aesKey.getEncoded());
73 | } catch (ArrayIndexOutOfBoundsException e) {
74 | throw new InvalidKeyException(e.getMessage());
75 | }
76 | return String.format("%s%s%s%s%s%s", CSE_PREFIX, CSE_VERSION, CSE_SEPARATOR, Base64.encodeBase64String(encryptedAESKey), CSE_SEPARATOR,
77 | Base64.encodeBase64String(result));
78 | }
79 |
80 | public static Cipher aesCipher() throws NoSuchPaddingException, NoSuchAlgorithmException, NoSuchProviderException {
81 | return Cipher.getInstance("AES/CCM/NoPadding", "BC");
82 | }
83 |
84 | public static Cipher rsaCipher(final String cseKeyText) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidKeySpecException, IllegalArgumentException {
85 | String[] cseKeyParts = cseKeyText.split("\\|");
86 | if (cseKeyParts.length != 2) {
87 | throw new InvalidKeyException("Invalid CSE Key: " + cseKeyText);
88 | }
89 | KeyFactory keyFactory = KeyFactory.getInstance("RSA");
90 |
91 | BigInteger keyComponent1, keyComponent2;
92 | try {
93 | keyComponent1 = new BigInteger(cseKeyParts[1].toLowerCase(Locale.getDefault()), 16);
94 | keyComponent2 = new BigInteger(cseKeyParts[0].toLowerCase(Locale.getDefault()), 16);
95 | } catch (NumberFormatException e) {
96 | throw new InvalidKeyException("Invalid CSE Key: " + cseKeyText);
97 | }
98 | RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(keyComponent1, keyComponent2);
99 | PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
100 |
101 | Cipher result = Cipher.getInstance("RSA/None/PKCS1Padding");
102 | result.init(Cipher.ENCRYPT_MODE, pubKey);
103 | return result;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/Client.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen;
18 |
19 | import com.github.woki.payments.adyen.action.*;
20 | import com.github.woki.payments.adyen.model.*;
21 |
22 | import java.util.Map;
23 |
24 | /**
25 | * @author Willian Oki <willian.oki@gmail.com>
26 | */
27 | public final class Client implements IClient {
28 | private ClientConfig config;
29 |
30 | private Client() {
31 | // disable default constructor
32 | }
33 |
34 | public interface IBuilder {
35 | IBuilder timeout(long connectionTimeout, long readTimeout);
36 |
37 | IBuilder connectionTimeout(long timeout);
38 |
39 | IBuilder readTimeout(long timeout);
40 |
41 | IBuilder extraParameters(final Map extraParameters);
42 |
43 | IBuilder proxyConfig(final String config);
44 |
45 | IBuilder encryptionKey(final String encryptionKey);
46 |
47 | IBuilder addExtraParameter(final String key, final String value);
48 |
49 | Client build();
50 | }
51 |
52 | public static IAccount endpoint(final String endpoint) {
53 | return new Builder(endpoint);
54 | }
55 |
56 | public interface IAccount {
57 | IBuilder credentials(final String username, final String password);
58 | }
59 |
60 | private static final class Builder implements IAccount, IBuilder {
61 | private Client instance = new Client();
62 |
63 | private Builder() {
64 | // disable default constructor
65 | }
66 |
67 | Builder(final String endpoint) {
68 | instance.config = new ClientConfig(endpoint);
69 | }
70 |
71 | @Override
72 | public IBuilder timeout(long connectionTimeout, long readTimeout) {
73 | instance.config.setConnectionTimeout((int) connectionTimeout);
74 | instance.config.setSocketTimeout((int) readTimeout);
75 | return this;
76 | }
77 |
78 | @Override
79 | public IBuilder connectionTimeout(long timeout) {
80 | instance.config.setConnectionTimeout((int) timeout);
81 | return this;
82 | }
83 |
84 | @Override
85 | public IBuilder readTimeout(long timeout) {
86 | instance.config.setSocketTimeout((int) timeout);
87 | return this;
88 | }
89 |
90 | @Override
91 | public IBuilder extraParameters(final Map extraParameters) {
92 | instance.config.setExtraParameters(extraParameters);
93 | return this;
94 | }
95 |
96 | @Override
97 | public IBuilder proxyConfig(final String config) {
98 | instance.config.setProxyConfig(config);
99 | return this;
100 | }
101 |
102 | @Override
103 | public IBuilder encryptionKey(final String encryptionKey) {
104 | instance.config.setEncryptionKey(encryptionKey);
105 | return this;
106 | }
107 |
108 | @Override
109 | public IBuilder addExtraParameter(final String key, final String value) {
110 | instance.config.addExtraParameter(key, value);
111 | return this;
112 | }
113 |
114 | @Override
115 | public Client build() {
116 | return instance;
117 | }
118 |
119 | @Override
120 | public IBuilder credentials(final String username, final String password) {
121 | instance.config.setUsername(username);
122 | instance.config.setPassword(password);
123 | return this;
124 | }
125 | }
126 |
127 | @Override
128 | public ClientConfig getClientConfig() {
129 | return config;
130 | }
131 |
132 | @Override
133 | public PaymentResponse authorise(final PaymentRequest request) {
134 | return Authorise.execute(config, request, false);
135 | }
136 |
137 | @Override
138 | public PaymentResponse authorise3ds(final PaymentRequest request) {
139 | return Authorise.execute(config, request, true);
140 | }
141 |
142 | @Override
143 | public PaymentResponse verifyBin(final PaymentRequest request) {
144 | return Authorise.execute(config, request, false);
145 | }
146 |
147 | @Override
148 | public ModificationResponse capture(final ModificationRequest request) {
149 | return Capture.execute(config, request);
150 | }
151 |
152 | @Override
153 | public ModificationResponse cancel(final ModificationRequest request) {
154 | return Cancel.execute(config, request);
155 | }
156 |
157 | @Override
158 | public ModificationResponse refund(final ModificationRequest request) {
159 | return Refund.execute(config, request);
160 | }
161 |
162 | @Override
163 | public ModificationResponse cancelOrRefund(final ModificationRequest request) {
164 | return CancelOrRefund.execute(config, request);
165 | }
166 |
167 | @Override
168 | public RecurringDisableResponse recurringDisable(final RecurringDisableRequest request) {
169 | return RecurringDisable.execute(config, request);
170 | }
171 |
172 | @Override
173 | public RecurringListDetailsResponse recurringListDetails(final RecurringListDetailsRequest request) {
174 | return RecurringListDetails.execute(config, request);
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/model/PaymentRequest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.model;
18 |
19 | import com.github.woki.payments.adyen.support.ToStringStyle;
20 | import lombok.Getter;
21 | import lombok.Setter;
22 | import org.apache.commons.lang3.StringUtils;
23 | import org.apache.commons.lang3.builder.ToStringBuilder;
24 |
25 | import java.io.Serializable;
26 | import java.util.Date;
27 | import java.util.HashMap;
28 | import java.util.Map;
29 |
30 | /**
31 | * @author Willian Oki <willian.oki@gmail.com>
32 | */
33 | @SuppressWarnings("serial")
34 | @Getter
35 | @Setter
36 | public class PaymentRequest implements Serializable {
37 | private Amount additionalAmount;
38 | private Map additionalData = new HashMap<>();
39 | private Amount amount;
40 | private Address billingAddress;
41 | private BrowserInfo browserInfo;
42 | private Integer captureDelayHours;
43 | private Date dateOfBirth;
44 | private ForexQuote dccQuote;
45 | private Address deliveryAddress;
46 | private String deliveryDate;
47 | private String deviceFingerprint;
48 | private Long fraudOffset;
49 | private Installments installments;
50 | private Integer mcc;
51 | private String merchantAccount;
52 | private String merchantOrderReference;
53 | private Map metadata = new HashMap<>();
54 | private String orderReference;
55 | private Recurring recurring;
56 | private String recurringProcessingModel;
57 | private String reference;
58 | private String selectedBrand;
59 | private String selectedRecurringDetailReference;
60 | private String sessionId;
61 | private String shopperEmail;
62 | private String shopperIP;
63 | private ShopperInteraction shopperInteraction;
64 | private String shopperLocale;
65 | private Name shopperName;
66 | private String shopperReference;
67 | private String shopperStatement;
68 | private String socialSecurityNumber;
69 | private String store;
70 | private String telephoneNumber;
71 | private String totalsGroup;
72 | private BankAccount bankAccount;
73 | private Card card;
74 | private String entityType;
75 | private ThreeDSecureData mpiData;
76 | private String nationality;
77 | private String md;
78 | private String paResponse;
79 |
80 | public void addAdditionalDataEntry(String key, String value) {
81 | if (StringUtils.isNotBlank(key)) {
82 | additionalData.put(key, value);
83 | }
84 | }
85 |
86 | // allowed values for the recurringProcessingModel field
87 | public enum ProcessingModel {
88 | SUBSCRIPTION( "Subscription"),
89 | CARDONFILE("CardOnFile");
90 |
91 | private final String modelName;
92 |
93 | ProcessingModel(String s) {
94 | modelName = s;
95 | }
96 |
97 | public String toString() {
98 | return this.modelName;
99 | }
100 | }
101 |
102 | @Override
103 | public String toString() {
104 | return new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE)
105 | .append("additionalAmount", additionalAmount)
106 | .append("additionalData", additionalData)
107 | .append("amount", amount)
108 | .append("bankAccount", bankAccount)
109 | .append("billingAddress", billingAddress)
110 | .append("browserInfo", browserInfo)
111 | .append("captureDelayHours", captureDelayHours)
112 | .append("card", card)
113 | .append("dateOfBirth", dateOfBirth)
114 | .append("dccQuote", dccQuote)
115 | .append("deliveryAddress", deliveryAddress)
116 | .append("deliveryDate", deliveryDate)
117 | .append("deviceFingerprint", deviceFingerprint)
118 | .append("fraudOffset", fraudOffset)
119 | .append("installments", installments)
120 | .append("mcc", mcc).append("md", md)
121 | .append("merchantAccount", merchantAccount)
122 | .append("merchantOrderReference", merchantOrderReference)
123 | .append("mpiData", mpiData)
124 | .append("orderReference", orderReference)
125 | .append("paResponse", paResponse)
126 | .append("recurring", recurring)
127 | .append("recurringProcessingModel", recurringProcessingModel)
128 | .append("reference", reference)
129 | .append("selectedBrand", selectedBrand)
130 | .append("selectedRecurringDetailReference", selectedRecurringDetailReference)
131 | .append("sessionId", sessionId)
132 | .append("shopperEmail", shopperEmail)
133 | .append("shopperIP", shopperIP)
134 | .append("shopperInteraction", shopperInteraction)
135 | .append("shopperLocale", shopperLocale)
136 | .append("shopperName", shopperName)
137 | .append("shopperReference", shopperReference)
138 | .append("shopperStatement", shopperStatement)
139 | .append("socialSecurityNumber", socialSecurityNumber)
140 | .append("telephoneNumber", telephoneNumber)
141 | .append("metadata", metadata)
142 | .append("store", store)
143 | .append("totalsGroup", totalsGroup)
144 | .append("entityType", entityType)
145 | .append("nationality", nationality).toString();
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Java integration for Adyen Payment System API
2 |
3 | [Adyen](http://www.adyen.com)
4 | > is the leading technology provider powering payments for global commerce in the 21st century.
5 | > With a seamless solution for mobile, online and in-store transactions, our technology enables merchants to accept almost any
6 | > type of payment, anywhere in the world.
7 |
8 | Adyen Payment System, **APS**, lies at the heart of its payment platform and it's the service a merchant integrates with for
9 | payment processing.
10 |
11 | **adyen-api** aims to be a cohesive and opinionated way for consuming APS' JSON messaging based services.
12 |
13 | ## Acknowledgements
14 | * [Chrischy](https://github.com/Golddragon152) - Proxy configuration and CSE
15 | * [Cleverbug](https://github.com/cleverbug) - Upgrade to consume Adyen's endpoints V18
16 |
17 | ## Milestones
18 | * 2.30.0cts - instituted CTS-local version of woki project, updated for v30 and recurringProcessingModel. This branch is not intended as a source for pulling code back to woki, but rather for creating local artifacts. See details in PAYMENT-704.
19 | * 2.25.1 - Fixed URIs for v25.
20 | * 2.25.0 - Updated to cover Adyen's endpoints new version, v25.
21 | * 2.18.1 - Bug fixes; code rationalizations; tests correctness; replaced boon's JSON ObjectMapper w/ Jackson's to properly handle atypical collections
22 | in the response.
23 | * 2.18.0 - Added full support for recurring resources.
24 | * 1.3.1 - Updated to cover Adyen's endpoints new version, v18; changes were across Card, BankAccount and PaymentRequest types.
25 | * 1.3.0 - Added support for CSE.
26 | * 1.2.1 - Added support for proxy configuration.
27 | * 1.0.0 - Initial.
28 |
29 | From version 2.* on the minor version number is used to show what Adyen's API version adyen-api is compliant with. For example,
30 | 2.25.0 relates to the first release covering Adyen's V25 and so on.
31 |
32 | ## Current version and Maven dependency
33 |
34 | ```xml
35 |
36 | com.github.woki
37 | payments-adyen-api
38 | 2.25.1
39 |
40 | ```
41 | See also this [Sample Client](http://github.com/woki/adyen-client) sample built upon **ayden-api**.
42 | It takes an authorization or modification request in YAML format and communicates with APS.
43 |
44 | ## Usage
45 |
46 | ### Client instantiation
47 | ```java
48 | Client client = Client
49 | .endpoint("https://pal-test.adyen.com")
50 | // .endpoint(APUtil.TEST_ENDPOINT)
51 | // .endpoint("https://pal-live.adyen.com")
52 | // .endpoint(APUtil.LIVE_ENDPOINT)
53 | .credentials(username, password)
54 | .build();
55 | ```
56 | In case you are behind a proxy just add .proxyConfig() to the composition, as follows
57 | ```java
58 | Client client = Client
59 | .endpoint("https://pal-test.adyen.com")
60 | .credentials(username, password)
61 | .proxyConfig("prxyusr:prxypass@prxysrvr:8888")
62 | .build();
63 | ```
64 | Notice that authentication is optional. For the example above the proxy configuration descriptor would then be like
65 | this: prxysrvr:8888. Either names or IP addresses can be used as the host name.
66 |
67 | #### CSE - Client Side Encryption
68 | For Adyen's CSE documentation and usage refer to [CSE Documentation](https://docs.adyen.com/developers/easy-encryption). Once you have generated a RSA public key
69 | just instantiate the Client like this:
70 | ```java
71 | Client client = Client
72 | .endpoint("https://pal-test.adyen.com")
73 | .credentials(username, password)
74 | .encryptionKey("10001|FBF867B24626DE756...") // key abbreviated for clarity sake
75 | .build();
76 | ```
77 | The Client will encrypt sensitive card information according to CSE specifications in case there's an encryption key defined.
78 |
79 | ### Authorisation
80 | ```java
81 | PaymentRequest request = PaymentRequestBuilder
82 | .merchantAccount(merchantAccount)
83 | .amount(new Amount(Currency.getInstance("EUR"), 1000L))
84 | .card(CardBuilder.number("4111111111111111").cvc("737").expiry(2016, 6).holder("Johnny Tester Visa").build())
85 | .reference(reference(ReferenceType.UUID))
86 | .shopper(NameBuilder.first("Willian").last("Oki").build(), "willian.oki@gmail.com", "127.0.0.1",
87 | "Test/DAPI/Authorisation/Willian Oki", ShopperInteraction.Ecommerce)
88 | .build();
89 | PaymentResponse response = client.authorise(request);
90 |
91 | // you can check if response is valid using response.isOk()
92 | ```
93 |
94 | ### Capture
95 | ```java
96 | ModificationRequest captureRequest = ModificationRequestBuilder
97 | .merchantAccount(merchantAccount)
98 | .modificationAmount(new Amount(Currency.getInstance("EUR"), 1000L))
99 | .originalReference(paymentResponse.getPspReference())
100 | .reference(reference(ReferenceType.UUID))
101 | .build();
102 | ModificationResponse captureResponse = client.capture(captureRequest);
103 |
104 | // you can check if response is valid using response.isOk()
105 | ```
106 |
107 | ### Cancel/Refund
108 | ```java
109 | ModificationRequest cancelRequest = ModificationRequestBuilder
110 | .merchantAccount(merchantAccount)
111 | .originalReference(paymentResponse.getPspReference())
112 | .reference(reference(ReferenceType.UUID))
113 | .build();
114 | ModificationResponse cancelResponse = client.cancel(cancelRequest);
115 |
116 | // you can check if response is valid using response.isOk()
117 | ```
118 | ```java
119 | ModificationRequest refundRequest = ModificationRequestBuilder
120 | .merchantAccount(merchantAccount)
121 | .modificationAmount(new Amount(Currency.getInstance("EUR"), 1000L))
122 | .originalReference(paymentResponse.getPspReference())
123 | .reference(reference(ReferenceType.UUID))
124 | .build();
125 | ModificationResponse refundResponse = client.refund(refundRequest);
126 |
127 | // you can check if response is valid using response.isOk()
128 | ```
129 | ```java
130 | ModificationRequest cancelOrRefundRequest = ModificationRequestBuilder
131 | .merchantAccount(merchantAccount)
132 | .originalReference(paymentResponse.getPspReference())
133 | .reference(reference(ReferenceType.UUID))
134 | .build();
135 | ModificationResponse cancelOrRefundResponse = client.cancelOrRefund(cancelOrRefundRequest);
136 |
137 | // you can check if response is valid using response.isOk()
138 | ```
139 |
140 | ### Recurring
141 |
142 | #### List Details
143 | ```java
144 | // possible ContractType: ONECLICK, RECURRING, PAYOUT
145 | RecurringListDetailsRequest req = new RecurringListDetailsRequest("yourMerchantAccount", ContractType.ONECLICK,
146 | "yourShopperReference");
147 | RecurringListDetailsResponse res = client.recurringListDetails(req);
148 |
149 | // you can check if response is valid using response.isOk()
150 |
151 | // ...
152 | ```
153 |
154 | #### Disable
155 | ```java
156 | RecurringDisableRequest req = new RecurringDisableRequest("yourContract", "yourMerchantAccount",
157 | "yourRecurringDetailReference", "yourShopperReference");
158 | RecurringDisableResponse res = client.recurringDisable(req);
159 |
160 | // you can check if response is valid using response.isOk()
161 |
162 | // ...
163 | ```
164 |
--------------------------------------------------------------------------------
/src/test/java/com/github/woki/payments/adyen/ClientPaymentsTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen;
18 |
19 | import com.github.woki.payments.adyen.error.APSAccessException;
20 | import com.github.woki.payments.adyen.model.*;
21 | import com.github.woki.payments.adyen.simulator.APS;
22 | import com.github.woki.payments.adyen.support.APUtil;
23 | import com.github.woki.payments.adyen.support.APUtil.ReferenceType;
24 | import org.junit.Before;
25 | import org.junit.Test;
26 | import org.junit.runner.RunWith;
27 | import org.springframework.boot.context.embedded.LocalServerPort;
28 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
29 | import org.springframework.boot.test.context.SpringBootTest;
30 | import org.springframework.test.context.junit4.SpringRunner;
31 |
32 | import java.util.Currency;
33 |
34 | import static com.github.woki.payments.adyen.support.APUtil.reference;
35 | import static org.hamcrest.MatcherAssert.assertThat;
36 | import static org.hamcrest.Matchers.is;
37 | import static org.hamcrest.Matchers.notNullValue;
38 |
39 | /**
40 | * @author Willian Oki <willian.oki@gmail.com>
41 | */
42 | @RunWith(SpringRunner.class)
43 | @SpringBootTest(classes = {APS.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
44 | @AutoConfigureMockMvc
45 | public class ClientPaymentsTest {
46 | private static final String PUBKEY_TEXT_ERR1 = "foo";
47 | private static final String PUBKEY_TEXT_ERR2 = "1|2";
48 |
49 | @LocalServerPort
50 | private int port;
51 |
52 | private Client client;
53 | private PaymentRequest req;
54 |
55 | @Before
56 | public void setUp() {
57 | client = Client
58 | .endpoint("http://localhost:" + port)
59 | .credentials("test_user", "test_password")
60 | .build();
61 | req = PaymentRequestBuilder
62 | .merchantAccount("test_merchant_account")
63 | .amount(new Amount(Currency.getInstance("USD"), 10))
64 | .build();
65 | }
66 |
67 | @Test(expected = APSAccessException.class)
68 | public void testClientCSEError() {
69 | Client cli = Client
70 | .endpoint(APUtil.TEST_ENDPOINT)
71 | .credentials("merchant", "password")
72 | .encryptionKey(PUBKEY_TEXT_ERR1)
73 | .build();
74 | cli.authorise(PaymentRequestBuilder
75 | .merchantAccount("mrchntacct")
76 | .amount(new Amount(Currency.getInstance("EUR"), 1000L))
77 | .card(CardBuilder
78 | .number("4111111111111111")
79 | .cvc("737")
80 | .expiry(2016, 6)
81 | .holder("Johnny Tester Visa")
82 | .build())
83 | .reference(reference(ReferenceType.UUID))
84 | .shopper(NameBuilder
85 | .first("Willian")
86 | .last("Oki")
87 | .build(), "willian.oki@gmail.com", "127.0.0.1", "Test/DAPI/Authorisation/Willian Oki", ShopperInteraction.Ecommerce)
88 | .build());
89 | }
90 |
91 | @Test(expected = APSAccessException.class)
92 | public void testClientCSEError2() {
93 | Client cli = Client.endpoint(APUtil.TEST_ENDPOINT).credentials("merchant", "password").encryptionKey(PUBKEY_TEXT_ERR2).build();
94 | cli.authorise(PaymentRequestBuilder.merchantAccount("mrchntacct").amount(new Amount(Currency.getInstance("EUR"), 1000L))
95 | .card(CardBuilder.number("4111111111111111").cvc("737").expiry(2016, 6).holder("Johnny Tester Visa").build())
96 | .reference(reference(ReferenceType.UUID)).shopper(NameBuilder.first("Willian").last("Oki").build(),
97 | "willian.oki@gmail.com", "127.0.0.1", "Test/DAPI/Authorisation/Willian Oki", ShopperInteraction.Ecommerce).build());
98 | }
99 |
100 | @Test
101 | public void testHttp200handling() throws Exception {
102 | req.setReference("gimme_200");
103 | PaymentResponse res = client.authorise(req);
104 | assertThat(res, notNullValue());
105 | assertThat(res.isOk(), is(true));
106 | res = client.authorise3ds(req);
107 | assertThat(res, notNullValue());
108 | assertThat(res.isOk(), is(true));
109 | }
110 |
111 | @Test
112 | public void testHttp500handling() throws Exception {
113 | req.setReference("gimme_500");
114 | PaymentResponse res = client.authorise(req);
115 | assertThat(res, notNullValue());
116 | assertThat(res.isInternalServerError(), is(true));
117 | assertThat(res.getMessage(), is("Unexpected error"));
118 | res = client.authorise3ds(req);
119 | assertThat(res, notNullValue());
120 | assertThat(res.isInternalServerError(), is(true));
121 | assertThat(res.getMessage(), is("Unexpected error"));
122 | }
123 |
124 | @Test
125 | public void testHttp400handling() throws Exception {
126 | req.setReference("gimme_400");
127 | PaymentResponse res = client.authorise(req);
128 | assertThat(res, notNullValue());
129 | assertThat(res.isBadRequest(), is(true));
130 | assertThat(res.getMessage(), is("Problem reading or understanding request"));
131 | res = client.authorise3ds(req);
132 | assertThat(res, notNullValue());
133 | assertThat(res.isBadRequest(), is(true));
134 | assertThat(res.getMessage(), is("Problem reading or understanding request"));
135 | }
136 |
137 | @Test
138 | public void testHttp422handling() throws Exception {
139 | req.setReference("gimme_422");
140 | PaymentResponse res = client.authorise(req);
141 | assertThat(res, notNullValue());
142 | assertThat(res.isUnprocessableEntity(), is(true));
143 | assertThat(res.getMessage(), is("Request validation error"));
144 | res = client.authorise3ds(req);
145 | assertThat(res, notNullValue());
146 | assertThat(res.isUnprocessableEntity(), is(true));
147 | assertThat(res.getMessage(), is("Request validation error"));
148 | }
149 |
150 | @Test
151 | public void testHttp401handling() throws Exception {
152 | req.setReference("gimme_401");
153 | PaymentResponse res = client.authorise(req);
154 | assertThat(res, notNullValue());
155 | assertThat(res.isUnauthorized(), is(true));
156 | assertThat(res.getMessage(), is("Authentication required"));
157 | res = client.authorise3ds(req);
158 | assertThat(res, notNullValue());
159 | assertThat(res.isUnauthorized(), is(true));
160 | assertThat(res.getMessage(), is("Authentication required"));
161 | }
162 |
163 | @Test
164 | public void testHttp403handling() throws Exception {
165 | req.setReference("gimme_403");
166 | PaymentResponse res = client.authorise(req);
167 | assertThat(res, notNullValue());
168 | assertThat(res.isForbidden(), is(true));
169 | assertThat(res.getMessage(), is("Insufficient permission to process request"));
170 | res = client.authorise3ds(req);
171 | assertThat(res, notNullValue());
172 | assertThat(res.isForbidden(), is(true));
173 | assertThat(res.getMessage(), is("Insufficient permission to process request"));
174 | }
175 |
176 | @Test
177 | public void testHttp404handling() throws Exception {
178 | req.setReference("gimme_404");
179 | PaymentResponse res = client.authorise(req);
180 | assertThat(res, notNullValue());
181 | assertThat(res.isNotFound(), is(true));
182 | assertThat(res.getMessage(), is("Service not found"));
183 | res = client.authorise3ds(req);
184 | assertThat(res, notNullValue());
185 | assertThat(res.isNotFound(), is(true));
186 | assertThat(res.getMessage(), is("Service not found"));
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/ClientConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen;
18 |
19 | import com.github.woki.payments.adyen.action.CSEUtil;
20 | import com.github.woki.payments.adyen.support.APService;
21 | import com.github.woki.payments.adyen.support.APUtil;
22 | import com.github.woki.payments.adyen.support.ToStringStyle;
23 | import org.apache.commons.lang3.StringUtils;
24 | import org.apache.commons.lang3.builder.ToStringBuilder;
25 | import org.apache.http.HttpHost;
26 | import org.apache.http.client.utils.URIUtils;
27 | import org.slf4j.Logger;
28 | import org.slf4j.LoggerFactory;
29 |
30 | import javax.crypto.Cipher;
31 | import java.net.URI;
32 | import java.util.HashMap;
33 | import java.util.Map;
34 | import java.util.regex.Matcher;
35 | import java.util.regex.Pattern;
36 |
37 | /**
38 | * @author Willian Oki <willian.oki@gmail.com>
39 | */
40 | public class ClientConfig {
41 | private HttpHost endpointHost;
42 | private int connectionTimeout;
43 | private int socketTimeout;
44 | private String proxyConfig;
45 | private HttpHost proxyHost;
46 | private String endpoint;
47 | private String username;
48 | private String password;
49 | private String proxyUsername, proxyPassword;
50 | private Map extraParameters = new HashMap<>();
51 | private String encryptionKey;
52 | private Cipher aesCipher;
53 | private Cipher rsaCipher;
54 |
55 | private static final Logger LOG = LoggerFactory.getLogger(ClientConfig.class);
56 | private static final Pattern PROXY_CONFIG_PATTERN = Pattern.compile("(.*):(.*)@([a-zA-Z0-9\\.:]+):(\\d+)|([a-zA-Z0-9\\.:]+):(\\d+)");
57 |
58 | /**
59 | * Constructor
60 | *
61 | * @param endpoint the endpoint; {@link APUtil#TEST_ENDPOINT} / {@link APUtil#LIVE_ENDPOINT}
62 | *
63 | * @throws IllegalArgumentException on invalid URI
64 | */
65 | public ClientConfig(final String endpoint) {
66 | if (StringUtils.isBlank(endpoint)) {
67 | throw new IllegalArgumentException("Invalid endpoint: " + endpoint);
68 | }
69 | endpointHost = URIUtils.extractHost(URI.create(endpoint));
70 | if (endpointHost == null) {
71 | throw new IllegalArgumentException("Invalid endpoint: " + endpoint);
72 | }
73 | this.endpoint = endpoint;
74 | }
75 |
76 | /**
77 | * connectionTimeout (millisecs) see http.connection.timeout (httpclient). 0 (default) means no timeout (blocking).
78 | *
79 | * @return the connection timeout
80 | */
81 | public int getConnectionTimeout() {
82 | return connectionTimeout;
83 | }
84 |
85 | void setConnectionTimeout(int connectionTimeout) {
86 | this.connectionTimeout = connectionTimeout;
87 | }
88 |
89 | /**
90 | * readTimeout (millisecs) see http.socket.timeout (httpclient). 0 (default) means no timeout (blocking).
91 | *
92 | * @return the read timeout
93 | */
94 | public int getSocketTimeout() {
95 | return socketTimeout;
96 | }
97 |
98 | void setSocketTimeout(int socketTimeout) {
99 | this.socketTimeout = socketTimeout;
100 | }
101 |
102 | public String getEndpointPort(final APService service) {
103 | return endpoint + service.getPath();
104 | }
105 |
106 | /**
107 | * @return the username
108 | */
109 | public String getUsername() {
110 | return username;
111 | }
112 |
113 | /**
114 | * @param username the username to set
115 | */
116 | void setUsername(final String username) {
117 | this.username = username;
118 | }
119 |
120 | /**
121 | * @return the password
122 | */
123 | public String getPassword() {
124 | return password;
125 | }
126 |
127 | /**
128 | * @param password the password to set
129 | */
130 | void setPassword(final String password) {
131 | this.password = password;
132 | }
133 |
134 | /**
135 | * @return extra parameters map
136 | */
137 | public Map getExtraParameters() {
138 | return extraParameters;
139 | }
140 |
141 | /**
142 | * @param extraParameters the extra parameters map to set
143 | */
144 | void setExtraParameters(final Map extraParameters) {
145 | this.extraParameters = extraParameters;
146 | }
147 |
148 | /**
149 | * @param key the extra parameter key
150 | * @param value the extra parameter value
151 | */
152 | void addExtraParameter(final String key, final String value) {
153 | extraParameters.put(key, value);
154 | }
155 |
156 | /**
157 | * Proxy's Hostname/IP, port and credentials formatted as follow:
158 | *
159 | * [user:password@]host:port
160 | * E.g.: prxyusr:prxypass@proxy:8888, prxyusr:prxypass@127.0.0.1:8888, proxy:8888, ...
161 | *
162 | *
163 | * @param proxyConfig the specification
164 | */
165 | void setProxyConfig(final String proxyConfig) {
166 | this.proxyConfig = proxyConfig;
167 | }
168 |
169 | public HttpHost getEndpointHost() {
170 | return endpointHost;
171 | }
172 |
173 | public boolean hasProxy() {
174 | return getProxyHost() != null;
175 | }
176 |
177 | public HttpHost getProxyHost() {
178 | if (proxyHost == null && proxyConfig != null) {
179 | Matcher matcher = PROXY_CONFIG_PATTERN.matcher(proxyConfig);
180 | if (matcher.matches() && matcher.groupCount() == 6) {
181 | if (matcher.group(1) == null) {
182 | proxyHost = HttpHost.create(matcher.group(5) + ":" + matcher.group(6));
183 | } else {
184 | proxyUsername = matcher.group(1);
185 | proxyPassword = matcher.group(2);
186 | proxyHost = HttpHost.create(matcher.group(3) + ":" + matcher.group(4));
187 | }
188 | }
189 | }
190 | return proxyHost;
191 | }
192 |
193 | public boolean isProxyAuthenticated() {
194 | return proxyUsername != null;
195 | }
196 |
197 | public String getProxyUsername() {
198 | return hasProxy() ? proxyUsername : null;
199 | }
200 |
201 | public String getProxyPassword() {
202 | return hasProxy() ? proxyPassword : null;
203 | }
204 |
205 | public String getEncryptionKey() {
206 | return encryptionKey;
207 | }
208 |
209 | void setEncryptionKey(final String encryptionKey) {
210 | this.encryptionKey = encryptionKey;
211 | }
212 |
213 | public Cipher getAesCipher() {
214 | if (StringUtils.isNotBlank(encryptionKey) && aesCipher == null) {
215 | try {
216 | aesCipher = CSEUtil.aesCipher();
217 | } catch (Exception e) {
218 | LOG.warn("Could not instantiate an AES Cipher", e);
219 | }
220 | }
221 | return aesCipher;
222 | }
223 |
224 | public Cipher getRsaCipher() {
225 | if (StringUtils.isNotBlank(encryptionKey) && rsaCipher == null) {
226 | try {
227 | rsaCipher = CSEUtil.rsaCipher(encryptionKey);
228 | } catch (Exception e) {
229 | LOG.warn("Could not instantiate an RSA Cipher. encryptionKey: {}", encryptionKey, e);
230 | }
231 | }
232 | return rsaCipher;
233 | }
234 |
235 | @Override
236 | public String toString() {
237 | return new ToStringBuilder(this, ToStringStyle.DEFAULT_STYLE)
238 | .append("endpointHost", endpointHost)
239 | .append("connectionTimeout", connectionTimeout)
240 | .append("socketTimeout", socketTimeout)
241 | .append("proxyConfig", proxyConfig)
242 | .append("proxyHost", proxyHost)
243 | .append("endpoint", endpoint)
244 | .append("username", username)
245 | .append("password", password)
246 | .append("proxyUsername", proxyUsername)
247 | .append("proxyPassword", proxyPassword)
248 | .append("extraParameters", extraParameters)
249 | .append("encryptionKey", encryptionKey).toString();
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | com.github.woki
5 | payments-adyen-api
6 | 2.25.2-SNAPSHOT
7 | jar
8 |
9 | ${project.groupId}:${project.artifactId}
10 | adyen-api is a cohesive and opinionated way for consuming APS' JSON messaging based services
11 |
12 | https://github.com/woki/adyen-api
13 |
14 |
15 | The Apache License, Version 2.0
16 | http://www.apache.org/licenses/LICENSE-2.0.txt
17 |
18 |
19 |
20 |
21 | Willian Oki
22 | willian.oki@gmail.com
23 |
24 |
25 |
26 | scm:git:git@github.com:woki/adyen-api.git
27 | scm:git:git@github.com:woki/adyen-api.git
28 | git@github.com:woki/adyen-api.git
29 |
30 |
31 |
32 | UTF-8
33 | UTF-8
34 | 1.7
35 | 3.6
36 | 1.3.2
37 | 4.5.3
38 | 1.22
39 | 2.8.9
40 | 1.16.16
41 | 1.57
42 | 3.6.1
43 | 3.0.1
44 | 2.10.4
45 | 1.6
46 | 1.6.8
47 |
48 |
49 |
50 |
51 | ossrh
52 | https://oss.sonatype.org/content/repositories/snapshots
53 |
54 |
55 |
56 |
57 |
58 |
59 | org.apache.maven.plugins
60 | maven-compiler-plugin
61 | ${maven-compiler-plugin.version}
62 |
63 | ${java.version}
64 | ${java.version}
65 |
66 |
67 |
68 | org.sonatype.plugins
69 | nexus-staging-maven-plugin
70 | ${nexus-staging-maven-plugin.version}
71 | true
72 |
73 | ossrh
74 | https://oss.sonatype.org/
75 | true
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | release
84 |
85 |
86 |
87 | org.apache.maven.plugins
88 | maven-source-plugin
89 | ${maven-source-plugin.version}
90 |
91 |
92 | attach-sources
93 |
94 | jar
95 |
96 |
97 |
98 |
99 |
100 | org.apache.maven.plugins
101 | maven-javadoc-plugin
102 | ${maven-javadoc-plugin.version}
103 |
104 |
105 | attach-javadocs
106 |
107 | jar
108 |
109 |
110 |
111 |
112 |
113 | org.apache.maven.plugins
114 | maven-gpg-plugin
115 | ${maven-gpg-plugin.version}
116 |
117 |
118 | sign-artifacts
119 | verify
120 |
121 | sign
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | org.springframework.boot
135 | spring-boot-dependencies
136 | 1.5.4.RELEASE
137 | pom
138 | import
139 |
140 |
141 |
142 |
143 |
144 |
145 | org.projectlombok
146 | lombok
147 | ${lombok.version}
148 | provided
149 |
150 |
151 | org.apache.commons
152 | commons-lang3
153 | ${commons-lang3.version}
154 |
155 |
156 | org.apache.commons
157 | commons-io
158 | ${commons-io.version}
159 |
160 |
161 | org.apache.httpcomponents
162 | httpcore
163 |
164 |
165 | org.apache.httpcomponents
166 | httpclient
167 |
168 |
169 | commons-logging
170 | commons-logging
171 |
172 |
173 |
174 |
175 | org.apache.httpcomponents
176 | fluent-hc
177 | ${fluent-hc.version}
178 |
179 |
180 | commons-logging
181 | commons-logging
182 |
183 |
184 |
185 |
186 |
187 | com.fasterxml.jackson.core
188 | jackson-databind
189 | ${jackson.version}
190 |
191 |
192 | com.fasterxml.jackson.core
193 | jackson-core
194 | ${jackson.version}
195 |
196 |
197 | com.fasterxml.jackson.core
198 | jackson-annotations
199 | ${jackson.version}
200 |
201 |
202 |
203 | org.hibernate
204 | hibernate-validator
205 |
206 |
207 | com.neovisionaries
208 | nv-i18n
209 | ${nv-i18n.version}
210 |
211 |
212 | org.slf4j
213 | slf4j-api
214 |
215 |
216 | org.bouncycastle
217 | bcprov-jdk15on
218 | ${bouncycastle.version}
219 |
220 |
221 |
222 |
223 | org.springframework.boot
224 | spring-boot-starter-web
225 | test
226 |
227 |
228 | org.springframework.boot
229 | spring-boot-starter-test
230 | test
231 |
232 |
233 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/action/Endpoint.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.action;
18 |
19 | import com.fasterxml.jackson.core.JsonProcessingException;
20 | import com.fasterxml.jackson.databind.ObjectMapper;
21 | import com.github.woki.payments.adyen.ClientConfig;
22 | import com.github.woki.payments.adyen.model.Card;
23 | import com.github.woki.payments.adyen.model.Error;
24 | import com.github.woki.payments.adyen.model.PaymentRequest;
25 | import com.github.woki.payments.adyen.support.APService;
26 | import org.apache.commons.lang3.StringUtils;
27 | import org.apache.http.HttpEntity;
28 | import org.apache.http.HttpResponse;
29 | import org.apache.http.HttpStatus;
30 | import org.apache.http.StatusLine;
31 | import org.apache.http.client.ResponseHandler;
32 | import org.apache.http.client.fluent.Executor;
33 | import org.apache.http.client.fluent.Request;
34 | import org.apache.http.entity.ContentType;
35 | import org.slf4j.Logger;
36 | import org.slf4j.LoggerFactory;
37 |
38 | import javax.crypto.BadPaddingException;
39 | import javax.crypto.IllegalBlockSizeException;
40 | import java.io.IOException;
41 | import java.io.InputStreamReader;
42 | import java.security.InvalidAlgorithmParameterException;
43 | import java.security.InvalidKeyException;
44 | import java.security.NoSuchAlgorithmException;
45 | import java.util.Date;
46 | import java.util.Map;
47 |
48 | import static org.apache.http.client.fluent.Request.Post;
49 |
50 | /**
51 | * @author Willian Oki <willian.oki@gmail.com>
52 | */
53 | final class Endpoint {
54 |
55 | private Endpoint() {
56 | // utility
57 | }
58 |
59 | private static final Logger LOG = LoggerFactory.getLogger(Endpoint.class);
60 | private static final ObjectMapper MAPPER = new ObjectMapper();
61 |
62 | private static Request createPost(APService service, ClientConfig config, Object request) {
63 | Request retval = Post(config.getEndpointPort(service));
64 | // configure conn timeout
65 | retval.connectTimeout(config.getConnectionTimeout());
66 | // configure socket timeout
67 | retval.socketTimeout(config.getSocketTimeout());
68 | // add json
69 | retval.addHeader("Content-Type", "application/json");
70 | retval.addHeader("Accept", "application/json");
71 | for (Map.Entry entry : config.getExtraParameters().entrySet()) {
72 | retval.addHeader(entry.getKey(), entry.getValue());
73 | }
74 | // add content
75 | String bodyString;
76 | try {
77 | bodyString = MAPPER.writeValueAsString(encrypt(config, request));
78 | } catch (Exception e) {
79 | throw new RuntimeException("CSE/JSON serialization error", e);
80 | }
81 | retval.bodyString(bodyString, ContentType.APPLICATION_JSON);
82 | if (config.hasProxy()) {
83 | retval.viaProxy(config.getProxyHost());
84 | }
85 | return retval;
86 | }
87 |
88 | private static Executor createExecutor(ClientConfig config) {
89 | Executor retval = Executor.newInstance();
90 | retval.auth(config.getEndpointHost(), config.getUsername(), config.getPassword());
91 | if (config.hasProxy() && config.isProxyAuthenticated()) {
92 | retval.auth(config.getProxyHost(), config.getProxyUsername(), config.getProxyPassword());
93 | }
94 | return retval;
95 | }
96 |
97 | private static Request createRequest(final ClientConfig config, final ReqType request, final Options opts) {
98 | if (LOG.isDebugEnabled()) {
99 | LOG.debug("config: {}, request: {}, options: {}", config, request, opts);
100 | }
101 | Request retval = Endpoint.createPost(APService.from(request, opts), config, request);
102 | if (LOG.isDebugEnabled()) {
103 | LOG.debug("retval: {}", retval);
104 | }
105 | return retval;
106 | }
107 |
108 | @SuppressWarnings("unchecked")
109 | private static ResType handleResponse(final HttpResponse response, final Class responseClass) throws IOException {
110 | ResType retval;
111 | HttpOutcome httpOutcome = handleHttpResponse(response, responseClass);
112 | if (httpOutcome.content != null) {
113 | retval = (ResType) httpOutcome.content;
114 | } else {
115 | if (httpOutcome.statusCode != HttpStatus.SC_OK) {
116 | LOG.warn("{} handling failed: {} - {}", responseClass.getSimpleName(), httpOutcome.statusCode, httpOutcome.message);
117 | }
118 | try {
119 | retval = responseClass.newInstance();
120 | retval.setStatus(httpOutcome.statusCode);
121 | retval.setMessage(httpOutcome.message);
122 | } catch (InstantiationException | IllegalAccessException e) {
123 | LOG.error("{} instantiation failure", responseClass.getSimpleName());
124 | throw new IOException(e);
125 | }
126 | }
127 | return retval;
128 | }
129 |
130 | static ResType invoke(final ClientConfig config, final ReqType request, final Class responseClass,
131 | final Options opts) throws IOException {
132 | Request httpRequest = createRequest(config, request, opts);
133 | Executor invoker = createExecutor(config);
134 | return invoker
135 | .execute(httpRequest)
136 | .handleResponse(
137 | new ResponseHandler() {
138 | public ResType handleResponse(HttpResponse response) throws IOException {
139 | ResType res = Endpoint.handleResponse(response, responseClass);
140 | if (LOG.isDebugEnabled()) {
141 | LOG.debug("response: {}", res);
142 | }
143 | return res;
144 | }
145 | }
146 | );
147 | }
148 |
149 | static ResType invoke(final ClientConfig config, final ReqType request, final Class responseClass)
150 | throws IOException {
151 | return invoke(config, request, responseClass, null);
152 | }
153 |
154 | private static HttpOutcome handleHttpResponse(final HttpResponse response, final Class responseClass) {
155 | final HttpOutcome retval = new HttpOutcome<>();
156 | final StatusLine status = response.getStatusLine();
157 | final HttpEntity entity = response.getEntity();
158 | try {
159 | retval.content = MAPPER.readValue(new InputStreamReader(entity.getContent()), responseClass);
160 | } catch (IOException e) {
161 | LOG.warn("Could no deserialize JSON from entity", e);
162 | }
163 | retval.statusCode = status.getStatusCode();
164 | switch (status.getStatusCode()) {
165 | case HttpStatus.SC_OK:
166 | retval.message = "Request processed normally";
167 | break;
168 | case HttpStatus.SC_BAD_REQUEST:
169 | retval.message = "Problem reading or understanding request";
170 | break;
171 | case HttpStatus.SC_UNPROCESSABLE_ENTITY:
172 | retval.message = "Request validation error";
173 | break;
174 | case HttpStatus.SC_UNAUTHORIZED:
175 | retval.message = "Authentication required";
176 | break;
177 | case HttpStatus.SC_FORBIDDEN:
178 | retval.message = "Insufficient permission to process request";
179 | break;
180 | case HttpStatus.SC_NOT_FOUND:
181 | retval.message = "Service not found";
182 | break;
183 | default:
184 | retval.message = "Unexpected error";
185 | }
186 | if (retval.content != null && StringUtils.isEmpty(retval.content.getMessage()))
187 | retval.content.setMessage(retval.message);
188 | return retval;
189 | }
190 |
191 | private static class HttpOutcome {
192 | int statusCode;
193 | String message;
194 | ResType content;
195 | }
196 |
197 | private static Object encrypt(ClientConfig config, Object original) throws BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalArgumentException, JsonProcessingException {
198 | if (! (original instanceof PaymentRequest)) {
199 | return original;
200 | }
201 | if (StringUtils.isBlank(config.getEncryptionKey())) {
202 | LOG.debug("CSE not enabled");
203 | return original;
204 | }
205 | Card card = ((PaymentRequest) original).getCard();
206 | if (card == null) {
207 | LOG.debug("CSE cannot be used: no card to encrypt");
208 | return original;
209 | }
210 | card.setGenerationtime(new Date());
211 | String jsonCard = MAPPER.writeValueAsString(card);
212 | String encryptedCard = CSEUtil.encrypt(config.getAesCipher(), config.getRsaCipher(), jsonCard);
213 | ((PaymentRequest) original).setCard(null);
214 | ((PaymentRequest) original).addAdditionalDataEntry(Card.CARD_ENCRYPTED_ADDITIONAL_DATA_KEY_NAME, encryptedCard);
215 | return original;
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Licensed under the Apache License, Version 2.0 (the "License");
2 | you may not use this file except in compliance with the License.
3 | You may obtain a copy of the License at
4 |
5 |
6 |
7 | Unless required by applicable law or agreed to in writing, software
8 | distributed under the License is distributed on an "AS IS" BASIS,
9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 | See the License for the specific language governing permissions and
11 | limitations under the License.
12 |
13 | ```
14 | -------------------------------------------------------------------------
15 | Apache License
16 | Version 2.0, January 2004
17 | http://www.apache.org/licenses/
18 |
19 |
20 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
21 |
22 | 1. Definitions.
23 |
24 | "License" shall mean the terms and conditions for use, reproduction,
25 | and distribution as defined by Sections 1 through 9 of this document.
26 |
27 | "Licensor" shall mean the copyright owner or entity authorized by
28 | the copyright owner that is granting the License.
29 |
30 | "Legal Entity" shall mean the union of the acting entity and all
31 | other entities that control, are controlled by, or are under common
32 | control with that entity. For the purposes of this definition,
33 | "control" means (i) the power, direct or indirect, to cause the
34 | direction or management of such entity, whether by contract or
35 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
36 | outstanding shares, or (iii) beneficial ownership of such entity.
37 |
38 | "You" (or "Your") shall mean an individual or Legal Entity
39 | exercising permissions granted by this License.
40 |
41 | "Source" form shall mean the preferred form for making modifications,
42 | including but not limited to software source code, documentation
43 | source, and configuration files.
44 |
45 | "Object" form shall mean any form resulting from mechanical
46 | transformation or translation of a Source form, including but
47 | not limited to compiled object code, generated documentation,
48 | and conversions to other media types.
49 |
50 | "Work" shall mean the work of authorship, whether in Source or
51 | Object form, made available under the License, as indicated by a
52 | copyright notice that is included in or attached to the work
53 | (an example is provided in the Appendix below).
54 |
55 | "Derivative Works" shall mean any work, whether in Source or Object
56 | form, that is based on (or derived from) the Work and for which the
57 | editorial revisions, annotations, elaborations, or other modifications
58 | represent, as a whole, an original work of authorship. For the purposes
59 | of this License, Derivative Works shall not include works that remain
60 | separable from, or merely link (or bind by name) to the interfaces of,
61 | the Work and Derivative Works thereof.
62 |
63 | "Contribution" shall mean any work of authorship, including
64 | the original version of the Work and any modifications or additions
65 | to that Work or Derivative Works thereof, that is intentionally
66 | submitted to Licensor for inclusion in the Work by the copyright owner
67 | or by an individual or Legal Entity authorized to submit on behalf of
68 | the copyright owner. For the purposes of this definition, "submitted"
69 | means any form of electronic, verbal, or written communication sent
70 | to the Licensor or its representatives, including but not limited to
71 | communication on electronic mailing lists, source code control systems,
72 | and issue tracking systems that are managed by, or on behalf of, the
73 | Licensor for the purpose of discussing and improving the Work, but
74 | excluding communication that is conspicuously marked or otherwise
75 | designated in writing by the copyright owner as "Not a Contribution."
76 |
77 | "Contributor" shall mean Licensor and any individual or Legal Entity
78 | on behalf of whom a Contribution has been received by Licensor and
79 | subsequently incorporated within the Work.
80 |
81 | 2. Grant of Copyright License. Subject to the terms and conditions of
82 | this License, each Contributor hereby grants to You a perpetual,
83 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
84 | copyright license to reproduce, prepare Derivative Works of,
85 | publicly display, publicly perform, sublicense, and distribute the
86 | Work and such Derivative Works in Source or Object form.
87 |
88 | 3. Grant of Patent License. Subject to the terms and conditions of
89 | this License, each Contributor hereby grants to You a perpetual,
90 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
91 | (except as stated in this section) patent license to make, have made,
92 | use, offer to sell, sell, import, and otherwise transfer the Work,
93 | where such license applies only to those patent claims licensable
94 | by such Contributor that are necessarily infringed by their
95 | Contribution(s) alone or by combination of their Contribution(s)
96 | with the Work to which such Contribution(s) was submitted. If You
97 | institute patent litigation against any entity (including a
98 | cross-claim or counterclaim in a lawsuit) alleging that the Work
99 | or a Contribution incorporated within the Work constitutes direct
100 | or contributory patent infringement, then any patent licenses
101 | granted to You under this License for that Work shall terminate
102 | as of the date such litigation is filed.
103 |
104 | 4. Redistribution. You may reproduce and distribute copies of the
105 | Work or Derivative Works thereof in any medium, with or without
106 | modifications, and in Source or Object form, provided that You
107 | meet the following conditions:
108 |
109 | (a) You must give any other recipients of the Work or
110 | Derivative Works a copy of this License; and
111 |
112 | (b) You must cause any modified files to carry prominent notices
113 | stating that You changed the files; and
114 |
115 | (c) You must retain, in the Source form of any Derivative Works
116 | that You distribute, all copyright, patent, trademark, and
117 | attribution notices from the Source form of the Work,
118 | excluding those notices that do not pertain to any part of
119 | the Derivative Works; and
120 |
121 | (d) If the Work includes a "NOTICE" text file as part of its
122 | distribution, then any Derivative Works that You distribute must
123 | include a readable copy of the attribution notices contained
124 | within such NOTICE file, excluding those notices that do not
125 | pertain to any part of the Derivative Works, in at least one
126 | of the following places: within a NOTICE text file distributed
127 | as part of the Derivative Works; within the Source form or
128 | documentation, if provided along with the Derivative Works; or,
129 | within a display generated by the Derivative Works, if and
130 | wherever such third-party notices normally appear. The contents
131 | of the NOTICE file are for informational purposes only and
132 | do not modify the License. You may add Your own attribution
133 | notices within Derivative Works that You distribute, alongside
134 | or as an addendum to the NOTICE text from the Work, provided
135 | that such additional attribution notices cannot be construed
136 | as modifying the License.
137 |
138 | You may add Your own copyright statement to Your modifications and
139 | may provide additional or different license terms and conditions
140 | for use, reproduction, or distribution of Your modifications, or
141 | for any such Derivative Works as a whole, provided Your use,
142 | reproduction, and distribution of the Work otherwise complies with
143 | the conditions stated in this License.
144 |
145 | 5. Submission of Contributions. Unless You explicitly state otherwise,
146 | any Contribution intentionally submitted for inclusion in the Work
147 | by You to the Licensor shall be under the terms and conditions of
148 | this License, without any additional terms or conditions.
149 | Notwithstanding the above, nothing herein shall supersede or modify
150 | the terms of any separate license agreement you may have executed
151 | with Licensor regarding such Contributions.
152 |
153 | 6. Trademarks. This License does not grant permission to use the trade
154 | names, trademarks, service marks, or product names of the Licensor,
155 | except as required for reasonable and customary use in describing the
156 | origin of the Work and reproducing the content of the NOTICE file.
157 |
158 | 7. Disclaimer of Warranty. Unless required by applicable law or
159 | agreed to in writing, Licensor provides the Work (and each
160 | Contributor provides its Contributions) on an "AS IS" BASIS,
161 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
162 | implied, including, without limitation, any warranties or conditions
163 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
164 | PARTICULAR PURPOSE. You are solely responsible for determining the
165 | appropriateness of using or redistributing the Work and assume any
166 | risks associated with Your exercise of permissions under this License.
167 |
168 | 8. Limitation of Liability. In no event and under no legal theory,
169 | whether in tort (including negligence), contract, or otherwise,
170 | unless required by applicable law (such as deliberate and grossly
171 | negligent acts) or agreed to in writing, shall any Contributor be
172 | liable to You for damages, including any direct, indirect, special,
173 | incidental, or consequential damages of any character arising as a
174 | result of this License or out of the use or inability to use the
175 | Work (including but not limited to damages for loss of goodwill,
176 | work stoppage, computer failure or malfunction, or any and all
177 | other commercial damages or losses), even if such Contributor
178 | has been advised of the possibility of such damages.
179 |
180 | 9. Accepting Warranty or Additional Liability. While redistributing
181 | the Work or Derivative Works thereof, You may choose to offer,
182 | and charge a fee for, acceptance of support, warranty, indemnity,
183 | or other liability obligations and/or rights consistent with this
184 | License. However, in accepting such obligations, You may act only
185 | on Your own behalf and on Your sole responsibility, not on behalf
186 | of any other Contributor, and only if You agree to indemnify,
187 | defend, and hold each Contributor harmless for any liability
188 | incurred by, or claims asserted against, such Contributor by reason
189 | of your accepting any such warranty or additional liability.
190 |
191 | END OF TERMS AND CONDITIONS
192 | ```
193 |
--------------------------------------------------------------------------------
/src/main/java/com/github/woki/payments/adyen/model/PaymentRequestBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 Willian Oki
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.github.woki.payments.adyen.model;
18 |
19 | import java.text.DateFormat;
20 | import java.text.SimpleDateFormat;
21 | import java.util.Date;
22 | import java.util.Map;
23 |
24 | /**
25 | * @author Willian Oki <willian.oki@gmail.com>
26 | */
27 | public final class PaymentRequestBuilder {
28 | private static final DateFormat DELIVERY_DATE_FMTR = new SimpleDateFormat("yyyy-MM-dd'T'00:00:00.000'Z'");
29 |
30 | private PaymentRequestBuilder() {
31 | // utility
32 | }
33 |
34 | public static IAmount merchantAccount(String account) {
35 | return new Builder(account);
36 | }
37 |
38 | public interface IAmount {
39 | IBuilder amount(Amount amount);
40 | }
41 |
42 | public interface IBuilder {
43 | IBuilder additionalAmount(Amount amount);
44 |
45 | IBuilder additionalDataEntry(String key, String value);
46 |
47 | IBuilder additionalData(Map fields);
48 |
49 | IBuilder bankAccount(BankAccount bankAccount);
50 |
51 | IBuilder billingAddress(Address address);
52 |
53 | IBuilder browserInfo(BrowserInfo info);
54 |
55 | IBuilder browserInfo(String userAgent, String acceptHeader);
56 |
57 | IBuilder captureDelayHours(Integer captureDelayHours);
58 |
59 | IBuilder card(Card card);
60 |
61 | IBuilder dccQuote(ForexQuote dccQuote);
62 |
63 | IBuilder deliveryAddress(Address deliveryAddress);
64 |
65 | IBuilder deliveryDate(Date date);
66 |
67 | IBuilder deviceFingerprint(String deviceFingerprint);
68 |
69 | IBuilder fraudOffset(Long fraudOffset);
70 |
71 | IBuilder installments(Integer value);
72 |
73 | IBuilder mcc(Integer mcc);
74 |
75 | IBuilder merchantOrderReference(String reference);
76 |
77 | IBuilder mpiData(ThreeDSecureData mpiData);
78 |
79 | IBuilder orderReference(String orderReference);
80 |
81 | IBuilder recurring(Recurring recurring);
82 |
83 | IBuilder reference(String reference);
84 |
85 | IBuilder selectedBrand(String brand);
86 |
87 | IBuilder selectedRecurringDetailReference(String selectedRecurringDetailReference);
88 |
89 | IBuilder sessionId(String sessionId);
90 |
91 | IBuilder shopperDateOfBirth(Date dateOfBirth);
92 |
93 | IBuilder shopperEmail(String email);
94 |
95 | IBuilder shopperIP(String ip);
96 |
97 | IBuilder shopperInteraction(ShopperInteraction interaction);
98 |
99 | IBuilder shopperLocale(String shopperLocale);
100 |
101 | IBuilder shopperReference(String shopperReference);
102 |
103 | IBuilder shopperName(Name name);
104 |
105 | IBuilder shopperStatement(String shopperStatement);
106 |
107 | IBuilder shopperSsn(String shopperSsn);
108 |
109 | IBuilder shopperTelephoneNumber(String shopperTelephoneNumber);
110 |
111 | IBuilder shopper(Name name, String email, String ip, String reference, ShopperInteraction interaction);
112 |
113 | IBuilder shopper(Name name, Date birth, String email, String ip, String reference, String ssn, String telephone, ShopperInteraction interaction, String locale, String
114 | statement);
115 |
116 | IBuilder md(String md);
117 |
118 | IBuilder paResponse(String paResponse);
119 |
120 | IBuilder metadata(Map metadata);
121 |
122 | IBuilder store(String store);
123 |
124 | IBuilder totalsGroup(String totalsGroup);
125 |
126 | IBuilder entityType(String entityType);
127 |
128 | IBuilder nationality(String nationality);
129 |
130 | PaymentRequest build();
131 | }
132 |
133 | private static final class Builder implements IAmount, IBuilder {
134 | private PaymentRequest request;
135 |
136 | Builder(String merchantAccount) {
137 | request = new PaymentRequest();
138 | request.setMerchantAccount(merchantAccount);
139 | }
140 |
141 | @Override
142 | public IBuilder card(Card card) {
143 | request.setCard(card);
144 | return this;
145 | }
146 |
147 | @Override
148 | public IBuilder shopperDateOfBirth(Date dateOfBirth) {
149 | request.setDateOfBirth(dateOfBirth);
150 | return this;
151 | }
152 |
153 | @Override
154 | public IBuilder dccQuote(ForexQuote dccQuote) {
155 | request.setDccQuote(dccQuote);
156 | return this;
157 | }
158 |
159 | @Override
160 | public IBuilder deliveryAddress(Address deliveryAddress) {
161 | request.setDeliveryAddress(deliveryAddress);
162 | return this;
163 | }
164 |
165 | @Override
166 | public IBuilder reference(String reference) {
167 | request.setReference(reference);
168 | return this;
169 | }
170 |
171 | @Override
172 | public IBuilder shopperEmail(String email) {
173 | request.setShopperEmail(email);
174 | return this;
175 | }
176 |
177 | @Override
178 | public IBuilder shopperIP(String ip) {
179 | request.setShopperIP(ip);
180 | return this;
181 | }
182 |
183 | @Override
184 | public IBuilder shopperReference(String reference) {
185 | request.setShopperReference(reference);
186 | return this;
187 | }
188 |
189 | @Override
190 | public IBuilder shopperInteraction(ShopperInteraction interaction) {
191 | request.setShopperInteraction(interaction);
192 | return this;
193 | }
194 |
195 | @Override
196 | public IBuilder shopperLocale(String shopperLocale) {
197 | request.setShopperLocale(shopperLocale);
198 | return null;
199 | }
200 |
201 | @Override
202 | public IBuilder amount(Amount amount) {
203 | request.setAmount(amount);
204 | return this;
205 | }
206 |
207 | @Override
208 | public PaymentRequest build() {
209 | return request;
210 | }
211 |
212 | @Override
213 | public IBuilder fraudOffset(Long offset) {
214 | request.setFraudOffset(offset);
215 | return this;
216 | }
217 |
218 | @Override
219 | public IBuilder mcc(Integer mcc) {
220 | request.setMcc(mcc);
221 | return this;
222 | }
223 |
224 | @Override
225 | public IBuilder merchantOrderReference(String reference) {
226 | request.setMerchantOrderReference(reference);
227 | return this;
228 | }
229 |
230 | @Override
231 | public IBuilder mpiData(ThreeDSecureData mpiData) {
232 | request.setMpiData(mpiData);
233 | return this;
234 | }
235 |
236 | @Override
237 | public IBuilder orderReference(String orderReference) {
238 | request.setOrderReference(orderReference);
239 | return this;
240 | }
241 |
242 | @Override
243 | public IBuilder recurring(Recurring recurring) {
244 | request.setRecurring(recurring);
245 | return this;
246 | }
247 |
248 | @Override
249 | public IBuilder selectedBrand(String brand) {
250 | request.setSelectedBrand(brand);
251 | return this;
252 | }
253 |
254 | @Override
255 | public IBuilder selectedRecurringDetailReference(String selectedRecurringDetailReference) {
256 | request.setSelectedRecurringDetailReference(selectedRecurringDetailReference);
257 | return this;
258 | }
259 |
260 | @Override
261 | public IBuilder sessionId(String sessionId) {
262 | request.setSessionId(sessionId);
263 | return this;
264 | }
265 |
266 | @Override
267 | public IBuilder additionalDataEntry(String key, String value) {
268 | request.addAdditionalDataEntry(key, value);
269 | return this;
270 | }
271 |
272 | @Override
273 | public IBuilder additionalData(Map fields) {
274 | request.setAdditionalData(fields);
275 | return this;
276 | }
277 |
278 | @Override
279 | public IBuilder bankAccount(BankAccount bankAccount) {
280 | request.setBankAccount(bankAccount);
281 | return this;
282 | }
283 |
284 | @Override
285 | public IBuilder browserInfo(String userAgent, String acceptHeader) {
286 | request.setBrowserInfo(new BrowserInfo(userAgent, acceptHeader));
287 | return this;
288 | }
289 |
290 | @Override
291 | public IBuilder captureDelayHours(Integer captureDelayHours) {
292 | request.setCaptureDelayHours(captureDelayHours);
293 | return this;
294 | }
295 |
296 | @Override
297 | public IBuilder browserInfo(BrowserInfo info) {
298 | request.setBrowserInfo(info);
299 | return this;
300 | }
301 |
302 | @Override
303 | public IBuilder billingAddress(Address address) {
304 | request.setBillingAddress(address);
305 | return this;
306 | }
307 |
308 | @Override
309 | public IBuilder additionalAmount(Amount amount) {
310 | request.setAdditionalAmount(amount);
311 | return this;
312 | }
313 |
314 | @Override
315 | public IBuilder installments(Integer value) {
316 | request.setInstallments(new Installments(value));
317 | return this;
318 | }
319 |
320 | @Override
321 | public IBuilder shopperName(Name name) {
322 | request.setShopperName(name);
323 | return this;
324 | }
325 |
326 | @Override
327 | public IBuilder deliveryDate(Date date) {
328 | request.setDeliveryDate(DELIVERY_DATE_FMTR.format(date));
329 | return this;
330 | }
331 |
332 | @Override
333 | public IBuilder deviceFingerprint(String deviceFingerprint) {
334 | request.setDeviceFingerprint(deviceFingerprint);
335 | return this;
336 | }
337 |
338 | @Override
339 | public IBuilder shopperStatement(String shopperStatement) {
340 | request.setShopperStatement(shopperStatement);
341 | return this;
342 | }
343 |
344 | @Override
345 | public IBuilder shopperSsn(String shopperSsn) {
346 | request.setSocialSecurityNumber(shopperSsn);
347 | return this;
348 | }
349 |
350 | @Override
351 | public IBuilder shopperTelephoneNumber(String shopperTelephoneNumber) {
352 | request.setTelephoneNumber(shopperTelephoneNumber);
353 | return this;
354 | }
355 |
356 | @Override
357 | public IBuilder shopper(Name name, String email, String ip, String reference, ShopperInteraction interaction) {
358 | return shopper(name, null, email, ip, reference, null, null, interaction, null, null);
359 | }
360 |
361 | @Override
362 | public IBuilder shopper(Name name, Date birth, String email, String ip, String reference, String ssn, String telephone, ShopperInteraction interaction, String locale,
363 | String statement) {
364 | request.setShopperName(name);
365 | request.setDateOfBirth(birth);
366 | request.setShopperEmail(email);
367 | request.setShopperIP(ip);
368 | request.setShopperReference(reference);
369 | request.setSocialSecurityNumber(ssn);
370 | request.setTelephoneNumber(telephone);
371 | request.setShopperInteraction(interaction);
372 | request.setShopperLocale(locale);
373 | request.setShopperStatement(statement);
374 | return this;
375 | }
376 |
377 | @Override
378 | public IBuilder md(String md) {
379 | request.setMd(md);
380 | return this;
381 | }
382 |
383 | @Override
384 | public IBuilder paResponse(String paResponse) {
385 | request.setPaResponse(paResponse);
386 | return this;
387 | }
388 |
389 | @Override
390 | public IBuilder metadata(Map metadata) {
391 | request.setMetadata(metadata);
392 | return this;
393 | }
394 |
395 | @Override
396 | public IBuilder store(String store) {
397 | request.setStore(store);
398 | return this;
399 | }
400 |
401 | @Override
402 | public IBuilder totalsGroup(String totalsGroup) {
403 | request.setTotalsGroup(totalsGroup);
404 | return this;
405 | }
406 |
407 | @Override
408 | public IBuilder entityType(String entityType) {
409 | request.setEntityType(entityType);
410 | return this;
411 | }
412 |
413 | @Override
414 | public IBuilder nationality(String nationality) {
415 | request.setNationality(nationality);
416 | return this;
417 | }
418 | }
419 | }
420 |
--------------------------------------------------------------------------------