Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/test/java/com/adyen/TerminalCloudAPITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@
import com.adyen.model.nexo.PaymentReceipt;
import com.adyen.model.nexo.PaymentRequest;
import com.adyen.model.nexo.PaymentResult;
import com.adyen.model.nexo.PaymentTransaction;
import com.adyen.model.nexo.Response;
import com.adyen.model.nexo.ResultType;
import com.adyen.model.nexo.SaleData;
import com.adyen.model.nexo.SaleToPOIRequest;
import com.adyen.model.nexo.SaleToPOIResponse;
import com.adyen.model.nexo.TransactionConditions;
import com.adyen.model.terminal.SaleToAcquirerData;
import com.adyen.model.terminal.TerminalAPIRequest;
import com.adyen.model.terminal.TerminalAPIResponse;
Expand Down Expand Up @@ -299,6 +301,50 @@ public void syncInputRequestSuccess() throws Exception {
assertFalse(requestAsJson.contains(": null"), "Found null value");
}

/**
*
* <p>When {@code setDebitPreferredFlag} is never called, the backing field stays {@code null} and
* Gson must omit it from the serialized JSON entirely. This allows the terminal to choose the
* payment type freely (DEBIT, CREDIT, or VOUCHER) rather than being forced to CREDIT by a
* spurious {@code "DebitPreferredFlag": false}.
*/
Comment on lines +304 to +310
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Javadoc comment has some minor formatting issues. There's an unnecessary blank line at the beginning, and the <p> tag is not needed for a single-paragraph comment. For better readability and adherence to standard Java conventions, you can simplify the comment block.

  /**
   * When {@code setDebitPreferredFlag} is never called, the backing field stays {@code null} and
   * Gson must omit it from the serialized JSON entirely. This allows the terminal to choose the
   * payment type freely (DEBIT, CREDIT, or VOUCHER) rather than being forced to CREDIT by a
   * spurious {@code "DebitPreferredFlag": false}.
   */

@Test
public void debitPreferredFlagOmittedFromJsonWhenNotSet() throws Exception {
Client client = createMockClientFromFile("mocks/terminal-api/payment-async-success");
TerminalCloudAPI terminalCloudApi = new TerminalCloudAPI(client);

TerminalAPIRequest terminalAPIRequest = createTerminalAPIPaymentRequest();

TransactionConditions transactionConditions = new TransactionConditions();
// Deliberately do NOT call transactionConditions.setDebitPreferredFlag(...)

PaymentTransaction paymentTransaction = new PaymentTransaction();
paymentTransaction.setTransactionConditions(transactionConditions);

terminalAPIRequest
.getSaleToPOIRequest()
.getPaymentRequest()
.setPaymentTransaction(paymentTransaction);
Comment on lines +321 to +327
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Instead of creating a new PaymentTransaction and replacing the existing one, it's better to retrieve the existing PaymentTransaction from the terminalAPIRequest and set the transactionConditions on it. This avoids accidentally discarding other important fields of PaymentTransaction (like AmountsReq) that are likely initialized in createTerminalAPIPaymentRequest(). This makes the test more robust and focused on the specific change being tested.

    terminalAPIRequest
        .getSaleToPOIRequest()
        .getPaymentRequest()
        .getPaymentTransaction()
        .setTransactionConditions(transactionConditions);


terminalCloudApi.async(terminalAPIRequest);

final ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(client.getHttpClient())
.request(
isNotNull(),
captor.capture(),
any(com.adyen.Config.class),
eq(true),
isNull(),
eq(ApiConstants.HttpMethod.POST),
isNull());

String requestAsJson = captor.getValue();
assertFalse(
requestAsJson.contains("DebitPreferredFlag"),
"DebitPreferredFlag must be absent from JSON when setter is never called");
}

/** Mocked response for stored value type for POST /sync */
@Test
public void syncPaymentRequestStoredValueSuccess() throws Exception {
Expand Down
36 changes: 36 additions & 0 deletions src/test/java/com/adyen/TerminalLocalAPITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,18 @@
import com.adyen.model.nexo.PaymentInstrumentType;
import com.adyen.model.nexo.PaymentReceipt;
import com.adyen.model.nexo.PaymentResult;
import com.adyen.model.nexo.PaymentTransaction;
import com.adyen.model.nexo.Response;
import com.adyen.model.nexo.ResultType;
import com.adyen.model.nexo.SaleData;
import com.adyen.model.nexo.SaleToPOIResponse;
import com.adyen.model.nexo.TransactionConditions;
import com.adyen.model.terminal.TerminalAPIRequest;
import com.adyen.model.terminal.TerminalAPIResponse;
import com.adyen.model.terminal.security.SecurityKey;
import com.adyen.service.TerminalLocalAPI;
import com.adyen.terminal.serialization.TerminalAPIGsonBuilder;
import com.google.gson.Gson;
import java.math.BigDecimal;
import java.util.List;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -135,6 +139,38 @@ public void syncPaymentRequestSuccess() throws Exception {
assertEquals(BigDecimal.ONE, paymentResult.getAmountsResp().getAuthorizedAmount());
}

/**
* Regression test for the DebitPreferredFlag workaround (Option A) on the local Terminal API.
*
* <p>{@link TerminalLocalAPI} serializes the request with {@link TerminalAPIGsonBuilder} before
* encrypting it. When {@code setDebitPreferredFlag} is never called, the backing field stays
* {@code null} and Gson must omit it from the JSON, allowing the terminal to choose the payment
* type freely (DEBIT, CREDIT, or VOUCHER).
*/
@Test
public void debitPreferredFlagOmittedFromJsonWhenNotSet() throws Exception {
Gson gson = TerminalAPIGsonBuilder.create();

TerminalAPIRequest terminalAPIRequest = createTerminalAPIPaymentRequest();

TransactionConditions transactionConditions = new TransactionConditions();
// Deliberately do NOT call transactionConditions.setDebitPreferredFlag(...)

PaymentTransaction paymentTransaction = new PaymentTransaction();
paymentTransaction.setTransactionConditions(transactionConditions);

terminalAPIRequest
.getSaleToPOIRequest()
.getPaymentRequest()
.setPaymentTransaction(paymentTransaction);

String json = gson.toJson(terminalAPIRequest);

assertFalse(
json.contains("DebitPreferredFlag"),
"DebitPreferredFlag must be absent from JSON when setter is never called");
}

/** Test success flow for local request that includes unexpected attributes */
@Test
public void syncPaymentRequestSuccessWithAdditionalAttributes() throws Exception {
Expand Down