Skip to content

Commit

Permalink
Add Klarna Payment Context Support (#403)
Browse files Browse the repository at this point in the history
  • Loading branch information
armando-rodriguez-cko authored May 8, 2024
1 parent f678ed2 commit 6a82f00
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 21 deletions.
3 changes: 2 additions & 1 deletion src/main/java/com/checkout/GsonSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ public class GsonSerializer implements Serializer {
.registerSubtype(com.checkout.payments.response.source.CurrencyAccountResponseSource.class, identifier(PaymentSourceType.CURRENCY_ACCOUNT)))
// Payment Contexts
.registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(com.checkout.payments.response.source.contexts.ResponseSource.class, CheckoutUtils.TYPE, true, com.checkout.payments.response.source.contexts.AlternativePaymentSourceResponse.class)
.registerSubtype(com.checkout.payments.response.source.contexts.PaymentContextsPayPayResponseSource.class, identifier(PaymentSourceType.PAYPAL)))
.registerSubtype(com.checkout.payments.response.source.contexts.PaymentContextsPayPalResponseSource.class, identifier(PaymentSourceType.PAYPAL))
.registerSubtype(com.checkout.payments.response.source.contexts.PaymentContextsKlarnaResponseSource.class, identifier(PaymentSourceType.KLARNA)))
// Payments - destination
.registerTypeAdapterFactory(RuntimeTypeAdapterFactory.of(com.checkout.payments.response.destination.PaymentResponseDestination.class, CheckoutUtils.TYPE, true, com.checkout.payments.response.destination.PaymentResponseAlternativeDestination.class)
.registerSubtype(com.checkout.payments.response.destination.PaymentResponseBankAccountDestination.class, identifier(PaymentDestinationType.BANK_ACCOUNT)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
@NoArgsConstructor
public final class PaymentContextDetailsResponse extends HttpMetadata {

private PaymentContextDetailsStatusType status;

@SerializedName("payment_request")
private PaymentContextsResponse paymentRequest;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.checkout.payments.contexts;

import com.google.gson.annotations.SerializedName;

public enum PaymentContextDetailsStatusType {

@SerializedName("Created")
CREATED,

@SerializedName("Approved")
APPROVED,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.checkout.payments.request.source.contexts;

import com.checkout.common.AccountHolder;
import com.checkout.common.PaymentSourceType;
import com.checkout.payments.request.source.AbstractRequestSource;
import com.google.gson.annotations.SerializedName;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public final class PaymentContextsKlarnaSource extends AbstractRequestSource {

@SerializedName("account_holder")
private AccountHolder accountHolder;


@Builder
private PaymentContextsKlarnaSource(final AccountHolder accountHolder) {
super(PaymentSourceType.KLARNA);
this.accountHolder = accountHolder;

}

public PaymentContextsKlarnaSource() {
super(PaymentSourceType.KLARNA);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@NoArgsConstructor
public final class PaymentContextsPayPayResponseSource extends AbstractPaymentContextsResponseSource implements ResponseSource {
public final class PaymentContextsKlarnaResponseSource extends AbstractPaymentContextsResponseSource implements ResponseSource {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.checkout.payments.response.source.contexts;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@NoArgsConstructor
public final class PaymentContextsPayPalResponseSource extends AbstractPaymentContextsResponseSource implements ResponseSource {

}
15 changes: 15 additions & 0 deletions src/test/java/com/checkout/SandboxTestFixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@
import org.hamcrest.Matcher;
import org.opentest4j.AssertionFailedError;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static java.util.Objects.requireNonNull;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
Expand Down Expand Up @@ -167,6 +171,17 @@ protected CardTokenResponse requestToken() {
return blocking(() -> tokensClient.requestCardToken(request));
}

protected <T> void checkErrorItem(final Supplier<CompletableFuture<T>> supplier, final String errorItem) {
try {
supplier.get().get();
fail();
} catch (final InterruptedException | ExecutionException exception) {
assertTrue(exception.getCause() instanceof CheckoutApiException);
final List<String> error_codes = (List<String>) ((CheckoutApiException) exception.getCause()).getErrorDetails().get("error_codes");
assertThat(error_codes, hasItem(errorItem));
}
}

public static class DisputesQueryResponseHasItems extends BaseMatcher<DisputesQueryResponse> {

@Override
Expand Down
45 changes: 42 additions & 3 deletions src/test/java/com/checkout/TestHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
import com.checkout.payments.ShippingDetails;
import com.checkout.payments.ThreeDSRequest;
import com.checkout.payments.contexts.PaymentContextsItems;
import com.checkout.payments.contexts.PaymentContextsProcessing;
import com.checkout.payments.contexts.PaymentContextsRequest;
import com.checkout.payments.hosted.HostedPaymentRequest;
import com.checkout.payments.links.PaymentLinkRequest;
import com.checkout.payments.request.source.contexts.PaymentContextsKlarnaSource;
import com.checkout.payments.request.source.contexts.PaymentContextsPayPalSource;
import lombok.SneakyThrows;

Expand Down Expand Up @@ -176,14 +178,15 @@ public static HostedPaymentRequest createHostedPaymentRequest(final String refer
.build();
}

public static PaymentContextsRequest createPaymentContextsRequest() {
public static PaymentContextsRequest createPaymentContextsPayPalRequest() {

final PaymentContextsPayPalSource source = PaymentContextsPayPalSource.builder().build();

final PaymentContextsItems item = PaymentContextsItems.builder()
.name("mask")
.quantity(1)
.unitPrice(2000)
.unitPrice(1000)
.totalAmount(1000)
.build();

final List<PaymentContextsItems> items = Arrays.asList(
Expand All @@ -192,7 +195,7 @@ public static PaymentContextsRequest createPaymentContextsRequest() {

return PaymentContextsRequest.builder()
.source(source)
.amount(2000L)
.amount(1000L)
.currency(Currency.EUR)
.paymentType(PaymentType.REGULAR)
.capture(true)
Expand All @@ -203,6 +206,42 @@ public static PaymentContextsRequest createPaymentContextsRequest() {
.build();
}

public static PaymentContextsRequest createPaymentContextsKlarnaRequest() {

final PaymentContextsKlarnaSource source = PaymentContextsKlarnaSource.builder()
.accountHolder(AccountHolder.builder()
.billingAddress(Address.builder()
.country(CountryCode.DE)
.build())
.build())
.build();

final PaymentContextsItems item = PaymentContextsItems.builder()
.name("mask")
.quantity(1)
.unitPrice(1000)
.totalAmount(1000)
.build();

final List<PaymentContextsItems> items = Arrays.asList(
item
);

final PaymentContextsProcessing processing = PaymentContextsProcessing.builder()
.locale("en-GB")
.build();

return PaymentContextsRequest.builder()
.source(source)
.amount(1000L)
.currency(Currency.EUR)
.paymentType(PaymentType.REGULAR)
.processingChannelId(requireNonNull(System.getenv("CHECKOUT_PROCESSING_CHANNEL_ID")))
.items(items)
.processing(processing)
.build();
}

public static PaymentRecipient createRecipient() {
return PaymentRecipient.builder()
.accountNumber("1234567")
Expand Down
11 changes: 0 additions & 11 deletions src/test/java/com/checkout/payments/AbstractPaymentsTestIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,4 @@ protected CardTokenResponse requestToken() {
return blocking(() -> tokensClient.requestCardToken(request));
}

protected <T> void checkErrorItem(final Supplier<CompletableFuture<T>> supplier, final String errorItem) {
try {
supplier.get().get();
fail();
} catch (final InterruptedException | ExecutionException exception) {
assertTrue(exception.getCause() instanceof CheckoutApiException);
final List<String> error_codes = (List<String>) ((CheckoutApiException) exception.getCause()).getErrorDetails().get("error_codes");
assertThat(error_codes, hasItem(errorItem));
}
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.checkout.payments.contexts;

import com.checkout.CheckoutApiException;
import com.checkout.PlatformType;
import com.checkout.SandboxTestFixture;
import com.checkout.TestHelper;
Expand All @@ -17,9 +18,9 @@ class PaymentContextsTestIT extends SandboxTestFixture {
}

@Test
void shouldMakeAPaymentContextRequest() {
void shouldMakeAPaymentContextPayPalRequest() {

final PaymentContextsRequest request = TestHelper.createPaymentContextsRequest();
final PaymentContextsRequest request = TestHelper.createPaymentContextsPayPalRequest();

final PaymentContextsRequestResponse response = blocking(() -> checkoutApi.paymentContextsClient().requestPaymentContexts(request));

Expand All @@ -28,24 +29,32 @@ void shouldMakeAPaymentContextRequest() {
assertNotNull(response.getPartnerMetadata().getOrderId());
}

@Test
void shouldMakeAPaymentContextKlarnaRequest() {

final PaymentContextsRequest request = TestHelper.createPaymentContextsKlarnaRequest();

checkErrorItem(() -> checkoutApi.paymentContextsClient().requestPaymentContexts(request), "apm_service_unavailable");
}

@Test
void shouldGetAPaymentContext() {

final PaymentContextsRequest request = TestHelper.createPaymentContextsRequest();
final PaymentContextsRequest request = TestHelper.createPaymentContextsPayPalRequest();

final PaymentContextsRequestResponse paymentContextsResponse = blocking(() -> checkoutApi.paymentContextsClient().requestPaymentContexts(request));

final PaymentContextDetailsResponse response = blocking(() -> checkoutApi.paymentContextsClient().getPaymentContextDetails(paymentContextsResponse.getId()));

assertNotNull(response);
assertNotNull(response.getPaymentRequest());
assertEquals(2000, response.getPaymentRequest().getAmount());
assertEquals(1000, response.getPaymentRequest().getAmount());
assertEquals(Currency.EUR, response.getPaymentRequest().getCurrency());
assertEquals(PaymentType.REGULAR, response.getPaymentRequest().getPaymentType());
assertEquals(true, response.getPaymentRequest().isCapture());
assertEquals("mask", response.getPaymentRequest().getItems().get(0).getName());
assertEquals(1, response.getPaymentRequest().getItems().get(0).getQuantity());
assertEquals(2000, response.getPaymentRequest().getItems().get(0).getUnitPrice());
assertEquals(1000, response.getPaymentRequest().getItems().get(0).getUnitPrice());
assertEquals("https://example.com/payments/success", response.getPaymentRequest().getSuccessUrl());
assertEquals("https://example.com/payments/fail", response.getPaymentRequest().getFailureUrl());
assertNotNull(response.getPartnerMetadata());
Expand Down

0 comments on commit 6a82f00

Please sign in to comment.