diff --git a/src/main/java/com/phonepe/sdk/pg/common/events/models/BaseEvent.java b/src/main/java/com/phonepe/sdk/pg/common/events/models/BaseEvent.java index ba906ba..0420cce 100644 --- a/src/main/java/com/phonepe/sdk/pg/common/events/models/BaseEvent.java +++ b/src/main/java/com/phonepe/sdk/pg/common/events/models/BaseEvent.java @@ -107,8 +107,10 @@ public static BaseEvent buildCustomCheckoutPayEvent( String apiPath, EventType eventName) { - // Extracting the instrument from the PgPaymentFlow - PgPaymentFlow pgPaymentFlow = (PgPaymentFlow) pgPaymentRequest.getPaymentFlow(); + // Extracting the instrument from the PgPaymentFlow (null-safe: non-PG flows bind to null) + PgPaymentFlow pgPaymentFlow = pgPaymentRequest.getPaymentFlow() instanceof PgPaymentFlow flow + ? flow + : null; // Extracting the targetApp from the paymentMode in PgPaymentFlow String targetApp = Optional.ofNullable(pgPaymentFlow) diff --git a/src/test/CallbackTest.java b/src/test/CallbackTest.java index 9399ec2..b9d756d 100644 --- a/src/test/CallbackTest.java +++ b/src/test/CallbackTest.java @@ -1274,4 +1274,15 @@ void testNotifyValidate() { jsonString); Assertions.assertEquals(expected, actual); } + + @Test + void testValidateCallbackStandardCheckoutMalformedJsonThrows() { + org.junit.jupiter.api.Assertions.assertThrows( + Exception.class, + () -> standardCheckoutClient.validateCallback( + "username", + "password", + "bc842c31a9e54efe320d30d948be61291f3ceee4766e36ab25fa65243cd76e0e", + "{not valid json")); + } } diff --git a/src/test/OrderStatusTest.java b/src/test/OrderStatusTest.java index f2b2f26..6e73752 100644 --- a/src/test/OrderStatusTest.java +++ b/src/test/OrderStatusTest.java @@ -765,4 +765,23 @@ void testSubscriptionOrderStatus() { Assertions.assertEquals(responseObject, actual); } + + @Test + void testGetOrderStatusWithoutDetailsFlagDefaultsToFalse() { + String url = String.format(StandardCheckoutConstants.ORDER_STATUS_API, merchantOrderId); + OrderStatusResponse orderStatusResponse = + OrderStatusResponse.builder().orderId("Order_DefaultFlag").state("COMPLETED").build(); + + addStubForGetRequest( + url, + ImmutableMap.of(StandardCheckoutConstants.ORDER_DETAILS, "false"), + getHeaders(), + HttpStatus.SC_OK, + ImmutableMap.of(), + orderStatusResponse); + + // 1-arg overload must delegate to getOrderStatus(id, false) + OrderStatusResponse actual = standardCheckoutClient.getOrderStatus(merchantOrderId); + Assertions.assertEquals(actual, orderStatusResponse); + } } diff --git a/src/test/RefundTest.java b/src/test/RefundTest.java index b655767..aa56f5f 100644 --- a/src/test/RefundTest.java +++ b/src/test/RefundTest.java @@ -234,4 +234,32 @@ void testRefundSubscriptionThrows404() { Assertions.assertEquals(404, phonePeException.getHttpStatusCode()); Assertions.assertEquals("Not Found", phonePeException.getMessage()); } + + @Test + void testGetRefundStatusStandardCheckoutFails() { + String refundId = "RefundIdNotFound"; + String url = String.format(StandardCheckoutConstants.REFUND_STATUS_API, refundId); + + PhonePeResponse errorResponse = + PhonePeResponse.>builder() + .code("REFUND_NOT_FOUND") + .message("Refund does not exist") + .data(Collections.emptyMap()) + .build(); + + addStubForGetRequest( + url, + ImmutableMap.of(), + getHeaders(), + HttpStatus.SC_NOT_FOUND, + ImmutableMap.of(), + errorResponse); + + PhonePeException exception = + assertThrows( + PhonePeException.class, + () -> standardCheckoutClient.getRefundStatus(refundId)); + Assertions.assertEquals(404, exception.getHttpStatusCode()); + Assertions.assertEquals("REFUND_NOT_FOUND", exception.getCode()); + } } diff --git a/src/test/TransactionTest.java b/src/test/TransactionTest.java index 5bc86e2..f1c86ef 100644 --- a/src/test/TransactionTest.java +++ b/src/test/TransactionTest.java @@ -113,4 +113,56 @@ void testTransactionSubscriptionClient() { OrderStatusResponse actual = subscriptionClient.getTransactionStatus(transactionId); Assertions.assertEquals(actual, transactionCheckStatusResponse); } + + @Test + void testTransactionStatusStandardCheckoutFails() { + String url = String.format(StandardCheckoutConstants.TRANSACTION_STATUS_API, transactionId); + + com.phonepe.sdk.pg.common.http.PhonePeResponse errorResponse = + com.phonepe.sdk.pg.common.http.PhonePeResponse.>builder() + .code("TRANSACTION_NOT_FOUND") + .message("Transaction not found") + .data(java.util.Collections.emptyMap()) + .build(); + + addStubForGetRequest( + url, + ImmutableMap.of(), + getHeaders(), + HttpStatus.SC_NOT_FOUND, + ImmutableMap.of(), + errorResponse); + + com.phonepe.sdk.pg.common.exception.PhonePeException exception = + org.junit.jupiter.api.Assertions.assertThrows( + com.phonepe.sdk.pg.common.exception.PhonePeException.class, + () -> standardCheckoutClient.getTransactionStatus(transactionId)); + Assertions.assertEquals(404, exception.getHttpStatusCode()); + } + + @Test + void testTransactionStatusCustomCheckoutFails() { + String url = String.format(CustomCheckoutConstants.TRANSACTION_STATUS_API, transactionId); + + com.phonepe.sdk.pg.common.http.PhonePeResponse errorResponse = + com.phonepe.sdk.pg.common.http.PhonePeResponse.>builder() + .code("TRANSACTION_NOT_FOUND") + .message("Transaction not found") + .data(java.util.Collections.emptyMap()) + .build(); + + addStubForGetRequest( + url, + ImmutableMap.of(), + getHeaders(), + HttpStatus.SC_NOT_FOUND, + ImmutableMap.of(), + errorResponse); + + com.phonepe.sdk.pg.common.exception.PhonePeException exception = + org.junit.jupiter.api.Assertions.assertThrows( + com.phonepe.sdk.pg.common.exception.PhonePeException.class, + () -> customCheckoutClient.getTransactionStatus(transactionId)); + Assertions.assertEquals(404, exception.getHttpStatusCode()); + } } diff --git a/src/test/customCheckoutTests/CustomCheckoutClientTest.java b/src/test/customCheckoutTests/CustomCheckoutClientTest.java index a2be690..864cff9 100644 --- a/src/test/customCheckoutTests/CustomCheckoutClientTest.java +++ b/src/test/customCheckoutTests/CustomCheckoutClientTest.java @@ -451,4 +451,85 @@ void testGetRefundStatusNotFound() { Assertions.assertEquals(404, exception.getHttpStatusCode()); Assertions.assertEquals("REFUND_NOT_FOUND", exception.getCode()); } + + // ─────────────────────────────────────────────────────────────────────── + // createSdkOrder() + // ─────────────────────────────────────────────────────────────────────── + + @Test + void testCreateSdkOrderSuccess() { + final String url = CustomCheckoutConstants.CREATE_ORDER_API; + + com.phonepe.sdk.pg.payments.v2.models.request.CreateSdkOrderRequest sdkRequest = + com.phonepe.sdk.pg.payments.v2.models.request.CreateSdkOrderRequest + .CustomCheckoutBuilder() + .merchantOrderId("SDK_ORDER_001") + .amount(2000L) + .build(); + + com.phonepe.sdk.pg.payments.v2.models.response.CreateSdkOrderResponse sdkResponse = + com.phonepe.sdk.pg.payments.v2.models.response.CreateSdkOrderResponse.builder() + .orderId("OMO_SDK_001") + .state("PENDING") + .token("sdk-token-abc") + .expireAt(java.time.Instant.now().getEpochSecond() + 600) + .build(); + + addStubForPostRequest( + url, getHeaders(), sdkRequest, HttpStatus.SC_OK, Maps.newHashMap(), sdkResponse); + + com.phonepe.sdk.pg.payments.v2.models.response.CreateSdkOrderResponse actual = + customCheckoutClient.createSdkOrder(sdkRequest); + Assertions.assertEquals(sdkResponse.getOrderId(), actual.getOrderId()); + Assertions.assertEquals(sdkResponse.getToken(), actual.getToken()); + } + + @Test + void testCreateSdkOrderFailure() { + final String url = CustomCheckoutConstants.CREATE_ORDER_API; + + com.phonepe.sdk.pg.payments.v2.models.request.CreateSdkOrderRequest sdkRequest = + com.phonepe.sdk.pg.payments.v2.models.request.CreateSdkOrderRequest + .CustomCheckoutBuilder() + .merchantOrderId("SDK_ORDER_FAIL_001") + .amount(2000L) + .build(); + + PhonePeResponse errorResponse = + PhonePeResponse.>builder() + .code("INTERNAL_SERVER_ERROR") + .message("Something went wrong") + .data(Collections.emptyMap()) + .build(); + + addStubForPostRequest( + url, + getHeaders(), + sdkRequest, + HttpStatus.SC_INTERNAL_SERVER_ERROR, + Maps.newHashMap(), + errorResponse); + + PhonePeException exception = + assertThrows( + PhonePeException.class, + () -> customCheckoutClient.createSdkOrder(sdkRequest)); + Assertions.assertEquals(500, exception.getHttpStatusCode()); + } + + // ─────────────────────────────────────────────────────────────────────── + // validateCallback() + // ─────────────────────────────────────────────────────────────────────── + + @Test + void testValidateCallbackMalformedJsonThrows() { + // sha256("username" + "password") = bc842c31a9e54efe320d30d948be61291f3ceee4766e36ab25fa65243cd76e0e + assertThrows( + Exception.class, + () -> customCheckoutClient.validateCallback( + "username", + "password", + "bc842c31a9e54efe320d30d948be61291f3ceee4766e36ab25fa65243cd76e0e", + "{not valid json")); + } } diff --git a/src/test/customCheckoutTests/CustomCheckoutPciAndDeviceOsTest.java b/src/test/customCheckoutTests/CustomCheckoutPciAndDeviceOsTest.java index f4e89d4..8917c67 100644 --- a/src/test/customCheckoutTests/CustomCheckoutPciAndDeviceOsTest.java +++ b/src/test/customCheckoutTests/CustomCheckoutPciAndDeviceOsTest.java @@ -177,4 +177,41 @@ void testPayDoesNotInjectDeviceOsHeaderWhenAbsent() { PgPaymentResponse actual = customCheckoutClient.pay(request); Assertions.assertEquals(response.getOrderId(), actual.getOrderId()); } + + // ── Non-PgPaymentFlow uses default host (resolveInstrument returns null) ── + + @Test + void testNonPgPaymentFlowUsesDefaultHost() throws Exception { + final String url = CustomCheckoutConstants.PAY_API; + + // Use SubscriptionNotifyRequestBuilder which sets SubscriptionRedemptionPaymentFlow — + // definitively not a PgPaymentFlow, so resolveInstrument() returns null. + PgPaymentRequest request = PgPaymentRequest.SubscriptionNotifyRequestBuilder() + .merchantOrderId("ORDER_NON_PG_FLOW_001") + .amount(1000L) + .merchantSubscriptionId("SUB001") + .autoDebit(false) + .build(); + + PgPaymentResponse response = PgPaymentResponse.builder() + .orderId("OMO_NON_PG_FLOW_001") + .state("PENDING") + .expireAt(java.time.Instant.now().getEpochSecond() + 600) + .build(); + + // Stub by URL only (no body match) since SubscriptionRedemptionPaymentFlow has + // extra fields; we only care that the request routes to the default host, not PCI host. + wireMockServer.stubFor( + com.github.tomakehurst.wiremock.client.WireMock.post( + com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo(url)) + .willReturn( + com.github.tomakehurst.wiremock.client.WireMock.aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(new com.fasterxml.jackson.databind.ObjectMapper() + .writeValueAsString(response)))); + + PgPaymentResponse actual = customCheckoutClient.pay(request); + Assertions.assertEquals(response.getOrderId(), actual.getOrderId()); + } }