diff --git a/README.md b/README.md index a4255de..b39cbe3 100644 --- a/README.md +++ b/README.md @@ -195,9 +195,10 @@ Contributions to PG Java SDK are welcome! Here's how you can contribute: 1. Fork the repository 2. Create a feature branch (`git checkout -b feature/amazing-feature`) -3. Commit your changes (`git commit -m 'Add some amazing feature'`) -4. Push to the branch (`git push origin feature/amazing-feature`) -5. Open a Pull Request +3. make sure to setup the pre-commit hook by running the command `pre-commit install` +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a Pull Request Please ensure your code follows the project's coding standards and includes appropriate tests. diff --git a/formatter.xml b/formatter.xml index bd267b4..ad003f7 100644 --- a/formatter.xml +++ b/formatter.xml @@ -1,7 +1,7 @@ - + diff --git a/pom.xml b/pom.xml index 2fcaa72..11ceac7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.phonepe pg-sdk-java - 2.2.1 + 2.2.2 B2B JAVA SDK https://github.com/PhonePe/phonepe-pg-sdk-java Phonepe PG JAVA SDK diff --git a/src/main/java/com/phonepe/sdk/pg/common/constants/Headers.java b/src/main/java/com/phonepe/sdk/pg/common/constants/Headers.java index 89ee09d..0f2f842 100644 --- a/src/main/java/com/phonepe/sdk/pg/common/constants/Headers.java +++ b/src/main/java/com/phonepe/sdk/pg/common/constants/Headers.java @@ -24,31 +24,32 @@ @Slf4j public class Headers { - private final Properties properties = new Properties(); - private final String PROPERTIES_FILE_NAME = "/sdk.properties"; + private final Properties properties = new Properties(); + private final String PROPERTIES_FILE_NAME = "/sdk.properties"; - static { - try (InputStream input = Headers.class.getResourceAsStream(PROPERTIES_FILE_NAME)) { - if (input == null) { - log.error("Could not find {}", PROPERTIES_FILE_NAME); - } else { - properties.load(input); - } - } catch (Exception e) { - log.error("Failed to load SDK properties: {}", e.getMessage()); - } - } + static { + try (InputStream input = Headers.class.getResourceAsStream(PROPERTIES_FILE_NAME)) { + if (input == null) { + log.error("Could not find {}", PROPERTIES_FILE_NAME); + } else { + properties.load(input); + } + } catch (Exception e) { + log.error("Failed to load SDK properties: {}", e.getMessage()); + } + } - public static final String API_VERSION = "V2"; - public static final String SUBSCRIPTION_API_VERSION = properties.getProperty("sdk.version"); - public static final String INTEGRATION = "API"; - public static final String SDK_VERSION = properties.getProperty("sdk.version"); - public static final String SDK_TYPE = "BACKEND_JAVA_SDK"; - public static final String SOURCE = "x-source"; - public static final String SOURCE_VERSION = "x-source-version"; - public static final String SOURCE_PLATFORM = "x-source-platform"; - public static final String SOURCE_PLATFORM_VERSION = "x-source-platform-version"; - public static final String OAUTH_AUTHORIZATION = "Authorization"; - public static final String CONTENT_TYPE = "Content-Type"; - public static final String ACCEPT = "Accept"; + public static final String API_VERSION = "V2"; + public static final String SUBSCRIPTION_API_VERSION = properties.getProperty("sdk.version"); + public static final String INTEGRATION = "API"; + public static final String SDK_VERSION = properties.getProperty("sdk.version"); + public static final String SDK_TYPE = "BACKEND_JAVA_SDK"; + public static final String SOURCE = "x-source"; + public static final String SOURCE_VERSION = "x-source-version"; + public static final String SOURCE_PLATFORM = "x-source-platform"; + public static final String SOURCE_PLATFORM_VERSION = "x-source-platform-version"; + public static final String OAUTH_AUTHORIZATION = "Authorization"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String ACCEPT = "Accept"; + public static final String X_DEVICE_OS = "x-device-os"; } diff --git a/src/main/java/com/phonepe/sdk/pg/common/models/request/PgPaymentRequest.java b/src/main/java/com/phonepe/sdk/pg/common/models/request/PgPaymentRequest.java index ce33a9b..145819f 100644 --- a/src/main/java/com/phonepe/sdk/pg/common/models/request/PgPaymentRequest.java +++ b/src/main/java/com/phonepe/sdk/pg/common/models/request/PgPaymentRequest.java @@ -15,6 +15,7 @@ */ package com.phonepe.sdk.pg.common.models.request; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.phonepe.sdk.pg.common.models.MetaInfo; @@ -45,325 +46,328 @@ @JsonInclude(Include.NON_NULL) public class PgPaymentRequest { - private String merchantOrderId; + private String merchantOrderId; - private Long amount; + private Long amount; - private MetaInfo metaInfo; + private MetaInfo metaInfo; - private PaymentFlow paymentFlow; + private PaymentFlow paymentFlow; - private List constraints; + private List constraints; - private DeviceContext deviceContext; - private Long expireAfter; - private Long expireAt; + private DeviceContext deviceContext; - private PgPaymentRequest( - String merchantOrderId, - Long amount, - MetaInfo metaInfo, - List constraints, - Long expireAfter) { - this.merchantOrderId = merchantOrderId; - this.amount = amount; - this.metaInfo = metaInfo; - this.constraints = constraints; - this.expireAfter = expireAfter; - } + private Long expireAfter; - @Builder( - builderClassName = "UpiIntentPayRequestBuilder", - builderMethodName = "UpiIntentPayRequestBuilder") - public PgPaymentRequest( - String merchantOrderId, - long amount, - MetaInfo metaInfo, - List constraints, - String deviceOS, - String merchantCallBackScheme, - String targetApp, - Long expireAfter) { - this(merchantOrderId, amount, metaInfo, constraints, expireAfter); - this.deviceContext = - DeviceContext.builder() - .deviceOS(deviceOS) - .merchantCallBackScheme(merchantCallBackScheme) - .build(); - this.paymentFlow = - PgPaymentFlow.builder() - .paymentMode( - IntentPaymentV2Instrument.builder().targetApp(targetApp).build()) - .build(); - } + private Long expireAt; - @Builder( - builderClassName = "UpiCollectPayViaVpaRequestBuilder", - builderMethodName = "UpiCollectPayViaVpaRequestBuilder") - public PgPaymentRequest( - long amount, - String merchantOrderId, - MetaInfo metaInfo, - List constraints, - String vpa, - String message, - Long expireAfter) { - this(merchantOrderId, amount, metaInfo, constraints, expireAfter); - this.paymentFlow = - PgPaymentFlow.builder() - .paymentMode( - CollectPaymentV2Instrument.builder() - .details( - VpaCollectPaymentDetails.builder().vpa(vpa).build()) - .message(message) - .build()) - .build(); - } + @JsonIgnore + private String xDeviceOs; - @Builder( - builderClassName = "UpiCollectPayViaPhoneNumberRequestBuilder", - builderMethodName = "UpiCollectPayViaPhoneNumberRequestBuilder") - public PgPaymentRequest( - long amount, - MetaInfo metaInfo, - String merchantOrderId, - String phoneNumber, - List constraints, - String message, - Long expireAfter) { - this(merchantOrderId, amount, metaInfo, constraints, expireAfter); - this.paymentFlow = - PgPaymentFlow.builder() - .paymentMode( - CollectPaymentV2Instrument.builder() - .details( - PhoneNumberCollectPaymentDetails.builder() - .phoneNumber(phoneNumber) - .build()) - .message(message) - .build()) - .build(); - } + private PgPaymentRequest( + String merchantOrderId, + Long amount, + MetaInfo metaInfo, + List constraints, + Long expireAfter) { + this.merchantOrderId = merchantOrderId; + this.amount = amount; + this.metaInfo = metaInfo; + this.constraints = constraints; + this.expireAfter = expireAfter; + } - @Builder(builderClassName = "UpiQrRequestBuilder", builderMethodName = "UpiQrRequestBuilder") - public PgPaymentRequest( - long amount, - MetaInfo metaInfo, - String merchantOrderId, - List constraints, - Long expireAfter) { - this(merchantOrderId, amount, metaInfo, constraints, expireAfter); - this.paymentFlow = - PgPaymentFlow.builder() - .paymentMode(UpiQrPaymentV2Instrument.builder().build()) - .build(); - } + @Builder( + builderClassName = "UpiIntentPayRequestBuilder", builderMethodName = "UpiIntentPayRequestBuilder") + public PgPaymentRequest( + String merchantOrderId, + long amount, + MetaInfo metaInfo, + List constraints, + String deviceOS, + String merchantCallBackScheme, + String targetApp, + Long expireAfter) { + this(merchantOrderId, amount, metaInfo, constraints, expireAfter); + this.deviceContext = DeviceContext.builder() + .deviceOS(deviceOS) + .merchantCallBackScheme(merchantCallBackScheme) + .build(); + this.paymentFlow = PgPaymentFlow.builder() + .paymentMode( + IntentPaymentV2Instrument.builder() + .targetApp(targetApp) + .build()) + .build(); + } - @Builder( - builderClassName = "NetBankingPayRequestBuilder", - builderMethodName = "NetBankingPayRequestBuilder") - public PgPaymentRequest( - long amount, - MetaInfo metaInfo, - List constraints, - String merchantOrderId, - String bankId, - String merchantUserId, - String redirectUrl, - Long expireAfter) { - this(merchantOrderId, amount, metaInfo, constraints, expireAfter); - this.paymentFlow = - PgPaymentFlow.builder() - .paymentMode( - NetBankingPaymentV2Instrument.builder() - .bankId(bankId) - .merchantUserId(merchantUserId) - .build()) - .merchantUrls(MerchantUrls.builder().redirectUrl(redirectUrl).build()) - .build(); - } + @Builder( + builderClassName = "UpiCollectPayViaVpaRequestBuilder", builderMethodName = "UpiCollectPayViaVpaRequestBuilder") + public PgPaymentRequest( + long amount, + String merchantOrderId, + MetaInfo metaInfo, + List constraints, + String vpa, + String message, + Long expireAfter, + String xDeviceOs) { + this(merchantOrderId, amount, metaInfo, constraints, expireAfter); + this.xDeviceOs = xDeviceOs; + this.paymentFlow = PgPaymentFlow.builder() + .paymentMode( + CollectPaymentV2Instrument.builder() + .details( + VpaCollectPaymentDetails.builder() + .vpa(vpa) + .build()) + .message(message) + .build()) + .build(); + } - @Builder( - builderClassName = "TokenPayRequestBuilder", - builderMethodName = "TokenPayRequestBuilder") - public PgPaymentRequest( - String merchantOrderId, - long amount, - long encryptionKeyId, - String authMode, - String encryptedToken, - String encryptedCvv, - String cryptogram, - String panSuffix, - String expiryMonth, - String expiryYear, - String redirectUrl, - String cardHolderName, - String merchantUserId, - MetaInfo metaInfo, - List constraints, - Long expireAfter) { - this(merchantOrderId, amount, metaInfo, constraints, expireAfter); - this.paymentFlow = - PgPaymentFlow.builder() - .paymentMode( - TokenPaymentV2Instrument.builder() - .merchantUserId(merchantUserId) - .authMode(authMode) - .tokenDetails( - TokenDetails.builder() - .cardHolderName(cardHolderName) - .cryptogram(cryptogram) - .encryptedCvv(encryptedCvv) - .encryptedToken(encryptedToken) - .encryptionKeyId(encryptionKeyId) - .panSuffix(panSuffix) - .expiry( - Expiry.builder() - .month(expiryMonth) - .year(expiryYear) - .build()) - .build()) - .build()) - .merchantUrls(MerchantUrls.builder().redirectUrl(redirectUrl).build()) - .build(); - } + @Builder( + builderClassName = "UpiCollectPayViaPhoneNumberRequestBuilder", builderMethodName = "UpiCollectPayViaPhoneNumberRequestBuilder") + public PgPaymentRequest( + long amount, + MetaInfo metaInfo, + String merchantOrderId, + String phoneNumber, + List constraints, + String message, + Long expireAfter, + String xDeviceOs) { + this(merchantOrderId, amount, metaInfo, constraints, expireAfter); + this.xDeviceOs = xDeviceOs; + this.paymentFlow = PgPaymentFlow.builder() + .paymentMode( + CollectPaymentV2Instrument.builder() + .details( + PhoneNumberCollectPaymentDetails.builder() + .phoneNumber(phoneNumber) + .build()) + .message(message) + .build()) + .build(); + } - @Builder( - builderClassName = "CardPayRequestBuilder", - builderMethodName = "CardPayRequestBuilder") - public PgPaymentRequest( - String merchantOrderId, - long amount, - long encryptionKeyId, - String authMode, - String encryptedCardNumber, - String encryptedCvv, - String expiryMonth, - String expiryYear, - String cardHolderName, - String merchantUserId, - MetaInfo metaInfo, - List constraints, - String redirectUrl, - Long expireAfter) { - this(merchantOrderId, amount, metaInfo, constraints, expireAfter); - this.paymentFlow = - PgPaymentFlow.builder() - .paymentMode( - CardPaymentV2Instrument.builder() - .merchantUserId(merchantUserId) - .authMode(authMode) - .cardDetails( - NewCardDetails.builder() - .cardHolderName(cardHolderName) - .encryptedCvv(encryptedCvv) - .encryptionKeyId(encryptionKeyId) - .encryptedCardNumber(encryptedCardNumber) - .expiry( - Expiry.builder() - .month(expiryMonth) - .year(expiryYear) - .build()) - .build()) - .build()) - .merchantUrls(MerchantUrls.builder().redirectUrl(redirectUrl).build()) - .build(); - } + @Builder(builderClassName = "UpiQrRequestBuilder", builderMethodName = "UpiQrRequestBuilder") + public PgPaymentRequest( + long amount, + MetaInfo metaInfo, + String merchantOrderId, + List constraints, + Long expireAfter) { + this(merchantOrderId, amount, metaInfo, constraints, expireAfter); + this.paymentFlow = PgPaymentFlow.builder() + .paymentMode(UpiQrPaymentV2Instrument.builder() + .build()) + .build(); + } - @Builder( - builderClassName = "SubscriptionSetupUpiIntentBuilder", - builderMethodName = "SubscriptionSetupUpiIntentBuilder") - public PgPaymentRequest( - String merchantOrderId, - String merchantSubscriptionId, - Long amount, - String deviceOS, - String merchantCallbackScheme, - String targetApp, - AuthWorkflowType authWorkflowType, - Long subscriptionExpireAt, - Long orderExpireAt, - AmountType amountType, - Frequency frequency, - MetaInfo metaInfo, - Long maxAmount, - List constraints) { - this(merchantOrderId, amount, metaInfo, constraints, null); - this.expireAt = orderExpireAt; - this.deviceContext = - DeviceContext.builder() - .deviceOS(deviceOS) - .merchantCallBackScheme(merchantCallbackScheme) - .build(); - this.paymentFlow = - SubscriptionSetupPaymentFlow.builder() - .merchantSubscriptionId(merchantSubscriptionId) - .amountType(amountType) - .authWorkflowType(authWorkflowType) - .expireAt(subscriptionExpireAt) - .frequency(frequency) - .maxAmount(maxAmount) - .paymentMode( - IntentPaymentV2Instrument.builder().targetApp(targetApp).build()) - .build(); - } + @Builder( + builderClassName = "NetBankingPayRequestBuilder", builderMethodName = "NetBankingPayRequestBuilder") + public PgPaymentRequest( + long amount, + MetaInfo metaInfo, + List constraints, + String merchantOrderId, + String bankId, + String merchantUserId, + String redirectUrl, + Long expireAfter) { + this(merchantOrderId, amount, metaInfo, constraints, expireAfter); + this.paymentFlow = PgPaymentFlow.builder() + .paymentMode( + NetBankingPaymentV2Instrument.builder() + .bankId(bankId) + .merchantUserId(merchantUserId) + .build()) + .merchantUrls(MerchantUrls.builder() + .redirectUrl(redirectUrl) + .build()) + .build(); + } - @Builder( - builderClassName = "SubscriptionSetupUpiCollectBuilder", - builderMethodName = "SubscriptionSetupUpiCollectBuilder") - public PgPaymentRequest( - String merchantOrderId, - String merchantSubscriptionId, - Long amount, - AuthWorkflowType authWorkflowType, - Long subscriptionExpireAt, - Long orderExpireAt, - AmountType amountType, - Frequency frequency, - MetaInfo metaInfo, - Long maxAmount, - String vpa, - String message, - List constraints) { - this(merchantOrderId, amount, metaInfo, constraints, null); - this.expireAt = orderExpireAt; - this.paymentFlow = - SubscriptionSetupPaymentFlow.builder() - .merchantSubscriptionId(merchantSubscriptionId) - .amountType(amountType) - .authWorkflowType(authWorkflowType) - .expireAt(subscriptionExpireAt) - .frequency(frequency) - .maxAmount(maxAmount) - .paymentMode( - CollectPaymentV2Instrument.builder() - .details( - VpaCollectPaymentDetails.builder().vpa(vpa).build()) - .message(message) - .build()) - .build(); - } + @Builder( + builderClassName = "TokenPayRequestBuilder", builderMethodName = "TokenPayRequestBuilder") + public PgPaymentRequest( + String merchantOrderId, + long amount, + long encryptionKeyId, + String authMode, + String encryptedToken, + String encryptedCvv, + String cryptogram, + String panSuffix, + String expiryMonth, + String expiryYear, + String redirectUrl, + String cardHolderName, + String merchantUserId, + MetaInfo metaInfo, + List constraints, + Long expireAfter) { + this(merchantOrderId, amount, metaInfo, constraints, expireAfter); + this.paymentFlow = PgPaymentFlow.builder() + .paymentMode( + TokenPaymentV2Instrument.builder() + .merchantUserId(merchantUserId) + .authMode(authMode) + .tokenDetails( + TokenDetails.builder() + .cardHolderName(cardHolderName) + .cryptogram(cryptogram) + .encryptedCvv(encryptedCvv) + .encryptedToken(encryptedToken) + .encryptionKeyId(encryptionKeyId) + .panSuffix(panSuffix) + .expiry( + Expiry.builder() + .month(expiryMonth) + .year(expiryYear) + .build()) + .build()) + .build()) + .merchantUrls(MerchantUrls.builder() + .redirectUrl(redirectUrl) + .build()) + .build(); + } - @Builder( - builderClassName = "SubscriptionNotifyRequestBuilder", - builderMethodName = "SubscriptionNotifyRequestBuilder") - public PgPaymentRequest( - String merchantOrderId, - Long amount, - Long expireAt, - MetaInfo metaInfo, - String merchantSubscriptionId, - boolean autoDebit, - RedemptionRetryStrategy redemptionRetryStrategy, - List constraints) { - this(merchantOrderId, amount, metaInfo, constraints, null); - this.expireAt = expireAt; - this.paymentFlow = - SubscriptionRedemptionPaymentFlow.builder() - .redemptionRetryStrategy(redemptionRetryStrategy) - .autoDebit(autoDebit) - .merchantSubscriptionId(merchantSubscriptionId) - .build(); - } + @Builder( + builderClassName = "CardPayRequestBuilder", builderMethodName = "CardPayRequestBuilder") + public PgPaymentRequest( + String merchantOrderId, + long amount, + long encryptionKeyId, + String authMode, + String encryptedCardNumber, + String encryptedCvv, + String expiryMonth, + String expiryYear, + String cardHolderName, + String merchantUserId, + MetaInfo metaInfo, + List constraints, + String redirectUrl, + Long expireAfter) { + this(merchantOrderId, amount, metaInfo, constraints, expireAfter); + this.paymentFlow = PgPaymentFlow.builder() + .paymentMode( + CardPaymentV2Instrument.builder() + .merchantUserId(merchantUserId) + .authMode(authMode) + .cardDetails( + NewCardDetails.builder() + .cardHolderName(cardHolderName) + .encryptedCvv(encryptedCvv) + .encryptionKeyId(encryptionKeyId) + .encryptedCardNumber(encryptedCardNumber) + .expiry( + Expiry.builder() + .month(expiryMonth) + .year(expiryYear) + .build()) + .build()) + .build()) + .merchantUrls(MerchantUrls.builder() + .redirectUrl(redirectUrl) + .build()) + .build(); + } + + @Builder( + builderClassName = "SubscriptionSetupUpiIntentBuilder", builderMethodName = "SubscriptionSetupUpiIntentBuilder") + public PgPaymentRequest( + String merchantOrderId, + String merchantSubscriptionId, + Long amount, + String deviceOS, + String merchantCallbackScheme, + String targetApp, + AuthWorkflowType authWorkflowType, + Long subscriptionExpireAt, + Long orderExpireAt, + AmountType amountType, + Frequency frequency, + MetaInfo metaInfo, + Long maxAmount, + List constraints) { + this(merchantOrderId, amount, metaInfo, constraints, null); + this.expireAt = orderExpireAt; + this.deviceContext = DeviceContext.builder() + .deviceOS(deviceOS) + .merchantCallBackScheme(merchantCallbackScheme) + .build(); + this.paymentFlow = SubscriptionSetupPaymentFlow.builder() + .merchantSubscriptionId(merchantSubscriptionId) + .amountType(amountType) + .authWorkflowType(authWorkflowType) + .expireAt(subscriptionExpireAt) + .frequency(frequency) + .maxAmount(maxAmount) + .paymentMode( + IntentPaymentV2Instrument.builder() + .targetApp(targetApp) + .build()) + .build(); + } + + @Builder( + builderClassName = "SubscriptionSetupUpiCollectBuilder", builderMethodName = "SubscriptionSetupUpiCollectBuilder") + public PgPaymentRequest( + String merchantOrderId, + String merchantSubscriptionId, + Long amount, + AuthWorkflowType authWorkflowType, + Long subscriptionExpireAt, + Long orderExpireAt, + AmountType amountType, + Frequency frequency, + MetaInfo metaInfo, + Long maxAmount, + String vpa, + String message, + List constraints) { + this(merchantOrderId, amount, metaInfo, constraints, null); + this.expireAt = orderExpireAt; + this.paymentFlow = SubscriptionSetupPaymentFlow.builder() + .merchantSubscriptionId(merchantSubscriptionId) + .amountType(amountType) + .authWorkflowType(authWorkflowType) + .expireAt(subscriptionExpireAt) + .frequency(frequency) + .maxAmount(maxAmount) + .paymentMode( + CollectPaymentV2Instrument.builder() + .details( + VpaCollectPaymentDetails.builder() + .vpa(vpa) + .build()) + .message(message) + .build()) + .build(); + } + + @Builder( + builderClassName = "SubscriptionNotifyRequestBuilder", builderMethodName = "SubscriptionNotifyRequestBuilder") + public PgPaymentRequest( + String merchantOrderId, + Long amount, + Long expireAt, + MetaInfo metaInfo, + String merchantSubscriptionId, + boolean autoDebit, + RedemptionRetryStrategy redemptionRetryStrategy, + List constraints) { + this(merchantOrderId, amount, metaInfo, constraints, null); + this.expireAt = expireAt; + this.paymentFlow = SubscriptionRedemptionPaymentFlow.builder() + .redemptionRetryStrategy(redemptionRetryStrategy) + .autoDebit(autoDebit) + .merchantSubscriptionId(merchantSubscriptionId) + .build(); + } } diff --git a/src/main/java/com/phonepe/sdk/pg/payments/v2/CustomCheckoutClient.java b/src/main/java/com/phonepe/sdk/pg/payments/v2/CustomCheckoutClient.java index 1cec884..8a106ed 100644 --- a/src/main/java/com/phonepe/sdk/pg/payments/v2/CustomCheckoutClient.java +++ b/src/main/java/com/phonepe/sdk/pg/payments/v2/CustomCheckoutClient.java @@ -46,360 +46,366 @@ public class CustomCheckoutClient extends BaseClient { - private List headers; + private List headers; - private CustomCheckoutClient( - final String clientId, - final String clientSecret, - final Integer clientVersion, - final Env env, - boolean shouldPublishEvents) { - super(clientId, clientSecret, clientVersion, env, shouldPublishEvents); - this.eventPublisher.send( - BaseEvent.buildInitClientEvent( - FlowType.PG, EventType.CUSTOM_CHECKOUT_CLIENT_INITIALIZED)); - this.prepareHeaders(); - } + private CustomCheckoutClient( + final String clientId, + final String clientSecret, + final Integer clientVersion, + final Env env, + boolean shouldPublishEvents) { + super(clientId, clientSecret, clientVersion, env, shouldPublishEvents); + this.eventPublisher.send( + BaseEvent.buildInitClientEvent( + FlowType.PG, EventType.CUSTOM_CHECKOUT_CLIENT_INITIALIZED)); + this.prepareHeaders(); + } - /** - * Generates a CustomCheckout Client for interacting with the PhonePe APIs - * - * @param clientId Unique client-id assigned to merchant by PhonePe - * @param clientSecret Secret provided by PhonePe - * @param clientVersion The client version used for secure transactions - * @param env Set to `Env.SANDBOX` for the SANDBOX environment or `Env.PRODUCTION` for the - * production environment. - * @return CustomCheckoutClient object for interacting with the PhonePe APIs - */ - public static CustomCheckoutClient getInstance( - final String clientId, - final String clientSecret, - final Integer clientVersion, - final Env env) - throws PhonePeException { - return getInstance(clientId, clientSecret, clientVersion, env, true); - } + /** + * Generates a CustomCheckout Client for interacting with the PhonePe APIs + * + * @param clientId Unique client-id assigned to merchant by PhonePe + * @param clientSecret Secret provided by PhonePe + * @param clientVersion The client version used for secure transactions + * @param env Set to `Env.SANDBOX` for the SANDBOX environment or `Env.PRODUCTION` for the + * production environment. + * @return CustomCheckoutClient object for interacting with the PhonePe APIs + */ + public static CustomCheckoutClient getInstance( + final String clientId, + final String clientSecret, + final Integer clientVersion, + final Env env) throws PhonePeException { + return getInstance(clientId, clientSecret, clientVersion, env, true); + } - /** - * Generates a CustomCheckout Client for interacting with the PhonePe APIs - * - * @param clientId received at the time of onboarding - * @param clientSecret received at the time of onboarding - * @param clientVersion received at the time of onboarding - * @param env environment to be used by the merchant - * @param shouldPublishEvents When true, events are sent to PhonePe providing smoother - * experience - * @return CustomCheckoutClient object for interacting with the PhonePe APIs - */ - public static CustomCheckoutClient getInstance( - final String clientId, - final String clientSecret, - final Integer clientVersion, - final Env env, - boolean shouldPublishEvents) - throws PhonePeException { - final boolean shouldPublishInProd = shouldPublishEvents && env == Env.PRODUCTION; - return new CustomCheckoutClient( - clientId, clientSecret, clientVersion, env, shouldPublishInProd); - } + /** + * Generates a CustomCheckout Client for interacting with the PhonePe APIs + * + * @param clientId received at the time of onboarding + * @param clientSecret received at the time of onboarding + * @param clientVersion received at the time of onboarding + * @param env environment to be used by the merchant + * @param shouldPublishEvents When true, events are sent to PhonePe providing smoother + * experience + * @return CustomCheckoutClient object for interacting with the PhonePe APIs + */ + public static CustomCheckoutClient getInstance( + final String clientId, + final String clientSecret, + final Integer clientVersion, + final Env env, + boolean shouldPublishEvents) throws PhonePeException { + final boolean shouldPublishInProd = shouldPublishEvents && env == Env.PRODUCTION; + return new CustomCheckoutClient( + clientId, clientSecret, clientVersion, env, shouldPublishInProd); + } - /** - * Initiate a Pay Order - * - * @param pgPaymentRequest Request Object Build using CustomCheckoutPayRequest Builder's - * @return CustomCheckoutPayResponse contains checkout page url and related details - */ - @SneakyThrows - public PgPaymentResponse pay(PgPaymentRequest pgPaymentRequest) { - String url = CustomCheckoutConstants.PAY_API; - try { - PgPaymentResponse pgPaymentResponse = - requestViaAuthRefresh( - HttpMethodType.POST, - pgPaymentRequest, - url, - null, - new TypeReference() {}, - headers); - this.eventPublisher.send( - BaseEvent.buildCustomCheckoutPayEvent( - EventState.SUCCESS, pgPaymentRequest, url, EventType.PAY_SUCCESS)); - return pgPaymentResponse; - } catch (Exception exception) { - this.eventPublisher.send( - BaseEvent.buildCustomCheckoutPayEvent( - EventState.FAILED, - pgPaymentRequest, - url, - EventType.PAY_FAILED, - exception)); - throw exception; - } - } + /** + * Initiate a Pay Order + * + * @param pgPaymentRequest Request Object Build using CustomCheckoutPayRequest Builder's + * @return CustomCheckoutPayResponse contains checkout page url and related details + */ + @SneakyThrows + public PgPaymentResponse pay(PgPaymentRequest pgPaymentRequest) { + String url = CustomCheckoutConstants.PAY_API; + try { + List requestHeaders = headers; + if (pgPaymentRequest.getXDeviceOs() != null) { + requestHeaders = new ArrayList<>(headers); + requestHeaders.add( + HttpHeaderPair.builder() + .key(Headers.X_DEVICE_OS) + .value(pgPaymentRequest.getXDeviceOs()) + .build()); + } + PgPaymentResponse pgPaymentResponse = requestViaAuthRefresh( + HttpMethodType.POST, + pgPaymentRequest, + url, + null, + new TypeReference() {}, + requestHeaders); + this.eventPublisher.send( + BaseEvent.buildCustomCheckoutPayEvent( + EventState.SUCCESS, pgPaymentRequest, url, EventType.PAY_SUCCESS)); + return pgPaymentResponse; + } catch (Exception exception) { + this.eventPublisher.send( + BaseEvent.buildCustomCheckoutPayEvent( + EventState.FAILED, + pgPaymentRequest, + url, + EventType.PAY_FAILED, + exception)); + throw exception; + } + } - /** - * Gets the status of the order - * - * @param merchantOrderId Order id generated by merchant - * @return OrderStatusResponse Response with order and transaction details - */ - @SneakyThrows - public OrderStatusResponse getOrderStatus(String merchantOrderId) { - return getOrderStatus(merchantOrderId, false); - } + /** + * Gets the status of the order + * + * @param merchantOrderId Order id generated by merchant + * @return OrderStatusResponse Response with order and transaction details + */ + @SneakyThrows + public OrderStatusResponse getOrderStatus(String merchantOrderId) { + return getOrderStatus(merchantOrderId, false); + } - /** - * Gets status of an order - * - * @param merchantOrderId Order id generated by merchant - * @param details true -> order status has all attempt details under paymentDetails list false - * -> order status has only latest attempt details under paymentDetails list - * @return OrderStatusResponse Response with order and transaction details - */ - @SneakyThrows - public OrderStatusResponse getOrderStatus(String merchantOrderId, boolean details) { - String url = String.format(CustomCheckoutConstants.ORDER_STATUS_API, merchantOrderId); - try { - OrderStatusResponse orderStatusResponse = - requestViaAuthRefresh( - HttpMethodType.GET, - null, - url, - Collections.singletonMap( - CustomCheckoutConstants.ORDER_DETAILS, - Boolean.toString(details)), - new TypeReference() {}, - headers); - this.eventPublisher.send( - BaseEvent.buildOrderStatusEvent( - EventState.SUCCESS, - merchantOrderId, - url, - FlowType.PG, - EventType.ORDER_STATUS_SUCCESS)); - return orderStatusResponse; - } catch (Exception exception) { - this.eventPublisher.send( - BaseEvent.buildOrderStatusEvent( - EventState.FAILED, - merchantOrderId, - url, - FlowType.PG, - EventType.ORDER_STATUS_FAILED, - exception)); - throw exception; - } - } + /** + * Gets status of an order + * + * @param merchantOrderId Order id generated by merchant + * @param details true -> order status has all attempt details under paymentDetails list false + * -> order status has only latest attempt details under paymentDetails list + * @return OrderStatusResponse Response with order and transaction details + */ + @SneakyThrows + public OrderStatusResponse getOrderStatus(String merchantOrderId, boolean details) { + String url = String.format(CustomCheckoutConstants.ORDER_STATUS_API, merchantOrderId); + try { + OrderStatusResponse orderStatusResponse = requestViaAuthRefresh( + HttpMethodType.GET, + null, + url, + Collections.singletonMap( + CustomCheckoutConstants.ORDER_DETAILS, + Boolean.toString(details)), + new TypeReference() {}, + headers); + this.eventPublisher.send( + BaseEvent.buildOrderStatusEvent( + EventState.SUCCESS, + merchantOrderId, + url, + FlowType.PG, + EventType.ORDER_STATUS_SUCCESS)); + return orderStatusResponse; + } catch (Exception exception) { + this.eventPublisher.send( + BaseEvent.buildOrderStatusEvent( + EventState.FAILED, + merchantOrderId, + url, + FlowType.PG, + EventType.ORDER_STATUS_FAILED, + exception)); + throw exception; + } + } - /** - * Initiate refund of an order - * - * @param refundRequest Request object build using RefundRequest builder - * @return RefundResponse contains refund details for an order - */ - @SneakyThrows - public RefundResponse refund(RefundRequest refundRequest) { - String url = CustomCheckoutConstants.REFUND_API; - try { - RefundResponse refundResponse = - requestViaAuthRefresh( - HttpMethodType.POST, - refundRequest, - url, - null, - new TypeReference() {}, - headers); - this.eventPublisher.send( - BaseEvent.buildRefundEvent( - EventState.SUCCESS, - refundRequest, - url, - FlowType.PG, - EventType.REFUND_SUCCESS)); - return refundResponse; - } catch (Exception exception) { - this.eventPublisher.send( - BaseEvent.buildRefundEvent( - EventState.FAILED, - refundRequest, - url, - FlowType.PG, - EventType.REFUND_FAILED, - exception)); - throw exception; - } - } + /** + * Initiate refund of an order + * + * @param refundRequest Request object build using RefundRequest builder + * @return RefundResponse contains refund details for an order + */ + @SneakyThrows + public RefundResponse refund(RefundRequest refundRequest) { + String url = CustomCheckoutConstants.REFUND_API; + try { + RefundResponse refundResponse = requestViaAuthRefresh( + HttpMethodType.POST, + refundRequest, + url, + null, + new TypeReference() {}, + headers); + this.eventPublisher.send( + BaseEvent.buildRefundEvent( + EventState.SUCCESS, + refundRequest, + url, + FlowType.PG, + EventType.REFUND_SUCCESS)); + return refundResponse; + } catch (Exception exception) { + this.eventPublisher.send( + BaseEvent.buildRefundEvent( + EventState.FAILED, + refundRequest, + url, + FlowType.PG, + EventType.REFUND_FAILED, + exception)); + throw exception; + } + } - /** - * Create order token for SDK integrated order requests - * - * @param createSdkOrderRequest Request object build using CreateSdkOrderRequest builder - * @return CreateSdkOrderResponse contains token details to be consumed by the UI - */ - @SneakyThrows - public CreateSdkOrderResponse createSdkOrder(CreateSdkOrderRequest createSdkOrderRequest) { - String url = CustomCheckoutConstants.CREATE_ORDER_API; - try { - CreateSdkOrderResponse createSdkOrderResponse = - requestViaAuthRefresh( - HttpMethodType.POST, - createSdkOrderRequest, - url, - null, - new TypeReference() {}, - headers); - this.eventPublisher.send( - BaseEvent.buildCreateSdkOrderEvent( - EventState.SUCCESS, - createSdkOrderRequest, - url, - FlowType.PG, - EventType.CREATE_SDK_ORDER_SUCCESS)); - return createSdkOrderResponse; - } catch (Exception exception) { - this.eventPublisher.send( - BaseEvent.buildCreateSdkOrderEvent( - EventState.FAILED, - createSdkOrderRequest, - url, - FlowType.PG, - EventType.CREATE_SDK_ORDER_FAILED, - exception)); - throw exception; - } - } + /** + * Create order token for SDK integrated order requests + * + * @param createSdkOrderRequest Request object build using CreateSdkOrderRequest builder + * @return CreateSdkOrderResponse contains token details to be consumed by the UI + */ + @SneakyThrows + public CreateSdkOrderResponse createSdkOrder(CreateSdkOrderRequest createSdkOrderRequest) { + String url = CustomCheckoutConstants.CREATE_ORDER_API; + try { + CreateSdkOrderResponse createSdkOrderResponse = requestViaAuthRefresh( + HttpMethodType.POST, + createSdkOrderRequest, + url, + null, + new TypeReference() {}, + headers); + this.eventPublisher.send( + BaseEvent.buildCreateSdkOrderEvent( + EventState.SUCCESS, + createSdkOrderRequest, + url, + FlowType.PG, + EventType.CREATE_SDK_ORDER_SUCCESS)); + return createSdkOrderResponse; + } catch (Exception exception) { + this.eventPublisher.send( + BaseEvent.buildCreateSdkOrderEvent( + EventState.FAILED, + createSdkOrderRequest, + url, + FlowType.PG, + EventType.CREATE_SDK_ORDER_FAILED, + exception)); + throw exception; + } + } - /** - * Get status of a transaction attempt - * - * @param transactionId Transaction attempt id generated by PhonePe - * @return OrderStatusResponse Response with order and transaction details - * @throws PhonePeException if any error occurs during the process - */ - @SneakyThrows - public OrderStatusResponse getTransactionStatus(String transactionId) { - final String url = - String.format(CustomCheckoutConstants.TRANSACTION_STATUS_API, transactionId); - try { - OrderStatusResponse orderStatusResponse = - requestViaAuthRefresh( - HttpMethodType.GET, - null, - url, - null, - new TypeReference() {}, - headers); - this.eventPublisher.send( - BaseEvent.buildTransactionStatusEvent( - EventState.SUCCESS, - transactionId, - url, - FlowType.PG, - EventType.TRANSACTION_STATUS_SUCCESS)); - return orderStatusResponse; - } catch (Exception exception) { - this.eventPublisher.send( - BaseEvent.buildTransactionStatusEvent( - EventState.FAILED, - transactionId, - url, - FlowType.PG, - EventType.TRANSACTION_STATUS_FAILED, - exception)); - throw exception; - } - } + /** + * Get status of a transaction attempt + * + * @param transactionId Transaction attempt id generated by PhonePe + * @return OrderStatusResponse Response with order and transaction details + * @throws PhonePeException if any error occurs during the process + */ + @SneakyThrows + public OrderStatusResponse getTransactionStatus(String transactionId) { + final String url = String.format(CustomCheckoutConstants.TRANSACTION_STATUS_API, transactionId); + try { + OrderStatusResponse orderStatusResponse = requestViaAuthRefresh( + HttpMethodType.GET, + null, + url, + null, + new TypeReference() {}, + headers); + this.eventPublisher.send( + BaseEvent.buildTransactionStatusEvent( + EventState.SUCCESS, + transactionId, + url, + FlowType.PG, + EventType.TRANSACTION_STATUS_SUCCESS)); + return orderStatusResponse; + } catch (Exception exception) { + this.eventPublisher.send( + BaseEvent.buildTransactionStatusEvent( + EventState.FAILED, + transactionId, + url, + FlowType.PG, + EventType.TRANSACTION_STATUS_FAILED, + exception)); + throw exception; + } + } - /** - * Get status of refund - * - * @param refundId Merchant Refund id for which you need the status - * @return RefundStatusResponse Refund status details - * @throws PhonePeException if any error occurs during the process - */ - @SneakyThrows - public RefundStatusResponse getRefundStatus(String refundId) { - final String url = String.format(CustomCheckoutConstants.REFUND_STATUS_API, refundId); - try { - RefundStatusResponse refundStatusResponse = - requestViaAuthRefresh( - HttpMethodType.GET, - null, - url, - null, - new TypeReference() {}, - headers); - this.eventPublisher.send( - BaseEvent.buildRefundStatusEvent( - EventState.SUCCESS, - refundId, - url, - FlowType.PG, - EventType.REFUND_STATUS_SUCCESS)); - return refundStatusResponse; - } catch (Exception exception) { - this.eventPublisher.send( - BaseEvent.buildRefundStatusEvent( - EventState.FAILED, - refundId, - url, - FlowType.PG, - EventType.REFUND_STATUS_FAILED, - exception)); - throw exception; - } - } + /** + * Get status of refund + * + * @param refundId Merchant Refund id for which you need the status + * @return RefundStatusResponse Refund status details + * @throws PhonePeException if any error occurs during the process + */ + @SneakyThrows + public RefundStatusResponse getRefundStatus(String refundId) { + final String url = String.format(CustomCheckoutConstants.REFUND_STATUS_API, refundId); + try { + RefundStatusResponse refundStatusResponse = requestViaAuthRefresh( + HttpMethodType.GET, + null, + url, + null, + new TypeReference() {}, + headers); + this.eventPublisher.send( + BaseEvent.buildRefundStatusEvent( + EventState.SUCCESS, + refundId, + url, + FlowType.PG, + EventType.REFUND_STATUS_SUCCESS)); + return refundStatusResponse; + } catch (Exception exception) { + this.eventPublisher.send( + BaseEvent.buildRefundStatusEvent( + EventState.FAILED, + refundId, + url, + FlowType.PG, + EventType.REFUND_STATUS_FAILED, + exception)); + throw exception; + } + } - /** - * Validate if the callback is valid - * - * @param username username set by the merchant on the dashboard - * @param password password set by the merchant on the dashboard - * @param authorization String data under `authorization` key of response headers - * @param responseBody Callback response body - * @return CallbackResponse Deserialized callback body - * @throws PhonePeException when callback is not valid - */ - @SneakyThrows - public CallbackResponse validateCallback( - String username, String password, String authorization, String responseBody) { - if (!CommonUtils.isCallbackValid(username, password, authorization)) { - throw new PhonePeException(417, "Invalid Callback"); - } - try { - return getObjectMapper().readValue(responseBody, CallbackResponse.class); - } catch (Exception exception) { - this.eventPublisher.send( - BaseEvent.buildCallbackSerializationFailedEvent( - EventState.FAILED, - FlowType.PG, - EventType.CALLBACK_SERIALIZATION_FAILED, - exception)); - throw exception; - } - } + /** + * Validate if the callback is valid + * + * @param username username set by the merchant on the dashboard + * @param password password set by the merchant on the dashboard + * @param authorization String data under `authorization` key of response headers + * @param responseBody Callback response body + * @return CallbackResponse Deserialized callback body + * @throws PhonePeException when callback is not valid + */ + @SneakyThrows + public CallbackResponse validateCallback( + String username, String password, String authorization, String responseBody) { + if (!CommonUtils.isCallbackValid(username, password, authorization)) { + throw new PhonePeException(417, "Invalid Callback"); + } + try { + return getObjectMapper().readValue(responseBody, CallbackResponse.class); + } catch (Exception exception) { + this.eventPublisher.send( + BaseEvent.buildCallbackSerializationFailedEvent( + EventState.FAILED, + FlowType.PG, + EventType.CALLBACK_SERIALIZATION_FAILED, + exception)); + throw exception; + } + } - /** Prepares the headers for CustomCheckout Client */ - private void prepareHeaders() { - this.headers = new ArrayList<>(); - headers.add( - HttpHeaderPair.builder().key(Headers.CONTENT_TYPE).value(APPLICATION_JSON).build()); - headers.add( - HttpHeaderPair.builder().key(Headers.SOURCE).value(Headers.INTEGRATION).build()); - headers.add( - HttpHeaderPair.builder() - .key(Headers.SOURCE_VERSION) - .value(Headers.API_VERSION) - .build()); - headers.add( - HttpHeaderPair.builder() - .key(Headers.SOURCE_PLATFORM) - .value(Headers.SDK_TYPE) - .build()); - headers.add( - HttpHeaderPair.builder() - .key(Headers.SOURCE_PLATFORM_VERSION) - .value(Headers.SDK_VERSION) - .build()); - } + /** Prepares the headers for CustomCheckout Client */ + private void prepareHeaders() { + this.headers = new ArrayList<>(); + headers.add( + HttpHeaderPair.builder() + .key(Headers.CONTENT_TYPE) + .value(APPLICATION_JSON) + .build()); + headers.add( + HttpHeaderPair.builder() + .key(Headers.SOURCE) + .value(Headers.INTEGRATION) + .build()); + headers.add( + HttpHeaderPair.builder() + .key(Headers.SOURCE_VERSION) + .value(Headers.API_VERSION) + .build()); + headers.add( + HttpHeaderPair.builder() + .key(Headers.SOURCE_PLATFORM) + .value(Headers.SDK_TYPE) + .build()); + headers.add( + HttpHeaderPair.builder() + .key(Headers.SOURCE_PLATFORM_VERSION) + .value(Headers.SDK_VERSION) + .build()); + } } diff --git a/src/main/java/com/phonepe/sdk/pg/payments/v2/models/request/PrefillUserLoginDetails.java b/src/main/java/com/phonepe/sdk/pg/payments/v2/models/request/PrefillUserLoginDetails.java new file mode 100644 index 0000000..0820c66 --- /dev/null +++ b/src/main/java/com/phonepe/sdk/pg/payments/v2/models/request/PrefillUserLoginDetails.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.phonepe.sdk.pg.payments.v2.models.request; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(Include.NON_NULL) +public class PrefillUserLoginDetails { + + String phoneNumber; +} diff --git a/src/main/java/com/phonepe/sdk/pg/payments/v2/models/request/StandardCheckoutPayRequest.java b/src/main/java/com/phonepe/sdk/pg/payments/v2/models/request/StandardCheckoutPayRequest.java index 98714bc..8fb372d 100644 --- a/src/main/java/com/phonepe/sdk/pg/payments/v2/models/request/StandardCheckoutPayRequest.java +++ b/src/main/java/com/phonepe/sdk/pg/payments/v2/models/request/StandardCheckoutPayRequest.java @@ -26,43 +26,48 @@ @JsonInclude(Include.NON_NULL) public class StandardCheckoutPayRequest { - private String merchantOrderId; - private long amount; - private MetaInfo metaInfo; - private PaymentFlow paymentFlow; - private Long expireAfter; - private Boolean disablePaymentRetry; + private String merchantOrderId; + private long amount; + private MetaInfo metaInfo; + private PaymentFlow paymentFlow; + private Long expireAfter; + private Boolean disablePaymentRetry; + private PrefillUserLoginDetails prefillUserLoginDetails; - /** - * Builds Standard Checkout Pay Request - * - * @param merchantOrderId The unique order ID generated by the merchant - * @param amount Order amount in paisa - * @param redirectUrl URL where user will be redirected after success/failed payment - * @param metaInfo Merchant defined meta info to store additional information - * @param message Payment message shown in APP for collect requests - */ - @Builder - public StandardCheckoutPayRequest( - String merchantOrderId, - long amount, - String redirectUrl, - MetaInfo metaInfo, - String message, - Long expireAfter, - PaymentModeConfig paymentModeConfig, - Boolean disablePaymentRetry) { - this.merchantOrderId = merchantOrderId; - this.amount = amount; - this.metaInfo = metaInfo; - this.expireAfter = expireAfter; - MerchantUrls merchantUrls = MerchantUrls.builder().redirectUrl(redirectUrl).build(); - this.setPaymentFlow( - PgCheckoutPaymentFlow.builder() - .message(message) - .merchantUrls(merchantUrls) - .paymentModeConfig(paymentModeConfig) - .build()); - this.disablePaymentRetry = disablePaymentRetry; - } + /** + * Builds Standard Checkout Pay Request + * + * @param merchantOrderId The unique order ID generated by the merchant + * @param amount Order amount in paisa + * @param redirectUrl URL where user will be redirected after success/failed payment + * @param metaInfo Merchant defined meta info to store additional information + * @param message Payment message shown in APP for collect requests + */ + @Builder + public StandardCheckoutPayRequest( + String merchantOrderId, + long amount, + String redirectUrl, + MetaInfo metaInfo, + String message, + Long expireAfter, + PaymentModeConfig paymentModeConfig, + Boolean disablePaymentRetry, + PrefillUserLoginDetails prefillUserLoginDetails) { + this.merchantOrderId = merchantOrderId; + this.amount = amount; + this.metaInfo = metaInfo; + this.expireAfter = expireAfter; + MerchantUrls merchantUrls = MerchantUrls.builder() + .redirectUrl(redirectUrl) + .build(); + this.setPaymentFlow( + PgCheckoutPaymentFlow.builder() + .message(message) + .merchantUrls(merchantUrls) + .paymentModeConfig(paymentModeConfig) + .build()); + this.disablePaymentRetry = disablePaymentRetry; + this.prefillUserLoginDetails = prefillUserLoginDetails; + } } diff --git a/src/test/PayTest.java b/src/test/PayTest.java index bc8ce17..3883836 100644 --- a/src/test/PayTest.java +++ b/src/test/PayTest.java @@ -16,6 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.collect.Maps; +import com.phonepe.sdk.pg.common.constants.Headers; import com.phonepe.sdk.pg.common.exception.PhonePeException; import com.phonepe.sdk.pg.common.http.PhonePeResponse; import com.phonepe.sdk.pg.common.models.request.PgPaymentRequest; @@ -29,11 +30,13 @@ import com.phonepe.sdk.pg.common.models.response.PgPaymentResponse; import com.phonepe.sdk.pg.payments.v2.customcheckout.CustomCheckoutConstants; import com.phonepe.sdk.pg.payments.v2.models.request.PaymentModeConfig; +import com.phonepe.sdk.pg.payments.v2.models.request.PrefillUserLoginDetails; import com.phonepe.sdk.pg.payments.v2.models.request.StandardCheckoutPayRequest; import com.phonepe.sdk.pg.payments.v2.models.response.StandardCheckoutPayResponse; import com.phonepe.sdk.pg.payments.v2.standardcheckout.StandardCheckoutConstants; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -163,6 +166,78 @@ void testCustomPay() { Assertions.assertEquals(pgPaymentResponse, actual); } + @SneakyThrows + @Test + void testCustomPayUpiCollectWithXDeviceOsHeader() { + final String url = CustomCheckoutConstants.PAY_API; + + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("MerchantOrderId") + .amount(100) + .vpa("user@upi") + .xDeviceOs("ANDROID") + .build(); + PgPaymentResponse pgPaymentResponse = + PgPaymentResponse.builder() + .orderId("OMO2403071446458436434329") + .state("PENDING") + .expireAt(java.time.Instant.now().getEpochSecond()) + .redirectUrl("mercury.com") + .build(); + + Map headers = new HashMap<>(getHeaders()); + headers.put(Headers.X_DEVICE_OS, "ANDROID"); + + addStubForPostRequest( + url, headers, request, HttpStatus.SC_OK, Maps.newHashMap(), pgPaymentResponse); + + PgPaymentResponse actual = customCheckoutClient.pay(request); + Assertions.assertEquals(pgPaymentResponse, actual); + } + + @SneakyThrows + @Test + void testCustomPayUpiCollectWithoutXDeviceOsHeader() { + final String url = CustomCheckoutConstants.PAY_API; + + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("MerchantOrderId") + .amount(100) + .vpa("user@upi") + .build(); + PgPaymentResponse pgPaymentResponse = + PgPaymentResponse.builder() + .orderId("OMO2403071446458436434329") + .state("PENDING") + .expireAt(java.time.Instant.now().getEpochSecond()) + .redirectUrl("mercury.com") + .build(); + + addStubForPostRequest( + url, getHeaders(), request, HttpStatus.SC_OK, Maps.newHashMap(), pgPaymentResponse); + + PgPaymentResponse actual = customCheckoutClient.pay(request); + Assertions.assertEquals(pgPaymentResponse, actual); + } + + @SneakyThrows + @Test + void testCustomPayUpiCollectXDeviceOsNotInRequestBody() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("MerchantOrderId") + .amount(100) + .vpa("user@upi") + .xDeviceOs("ANDROID") + .build(); + + String json = objectMapper.writeValueAsString(request); + Assertions.assertFalse(json.contains("xDeviceOs"), + "xDeviceOs should not be serialized into the request body"); + } + @SneakyThrows @Test void testStandardCheckoutPayWithDisablePaymentRetryTrue() { @@ -263,4 +338,113 @@ void testStandardCheckoutPayWithDisablePaymentRetryNull() { Assertions.assertEquals(standardCheckoutResponse, actual); Assertions.assertNull(standardCheckoutPayRequest.getDisablePaymentRetry()); } + + @SneakyThrows + @Test + void testStandardCheckoutPayWithPrefillUserLoginDetails() { + final String url = StandardCheckoutConstants.PAY_API; + String redirectUrl = "https://redirectUrl.com"; + String prefiledMobileNumber = "+919090909009"; + + PrefillUserLoginDetails prefillUserLoginDetails = PrefillUserLoginDetails.builder() + .phoneNumber("+919090909009") + .build(); + StandardCheckoutPayRequest standardCheckoutPayRequest = + StandardCheckoutPayRequest.builder() + .merchantOrderId(merchantOrderId) + .amount(amount) + .prefillUserLoginDetails(prefillUserLoginDetails) + .redirectUrl(redirectUrl) + .build(); + StandardCheckoutPayResponse standardCheckoutResponse = + StandardCheckoutPayResponse.builder() + .orderId(String.valueOf(java.time.Instant.now().getEpochSecond())) + .state("PENDING") + .expireAt(java.time.Instant.now().getEpochSecond()) + .redirectUrl("https://google.com") + .build(); + Map headers = getHeaders(); + + addStubForPostRequest( + url, + headers, + standardCheckoutPayRequest, + HttpStatus.SC_OK, + Maps.newHashMap(), + standardCheckoutResponse); + StandardCheckoutPayResponse actual = standardCheckoutClient.pay(standardCheckoutPayRequest); + Assertions.assertEquals(standardCheckoutResponse, actual); + Assertions.assertNotNull(standardCheckoutPayRequest.getPrefillUserLoginDetails()); + Assertions.assertEquals(prefiledMobileNumber, standardCheckoutPayRequest.getPrefillUserLoginDetails().getPhoneNumber()); + } + + @SneakyThrows + @Test + void testStandardCheckoutPayWithEmptyPrefillUserLoginDetails() { + final String url = StandardCheckoutConstants.PAY_API; + String redirectUrl = "https://redirectUrl.com"; + + PrefillUserLoginDetails prefillUserLoginDetails = PrefillUserLoginDetails.builder() + .build(); + StandardCheckoutPayRequest standardCheckoutPayRequest = + StandardCheckoutPayRequest.builder() + .merchantOrderId(merchantOrderId) + .amount(amount) + .prefillUserLoginDetails(prefillUserLoginDetails) + .redirectUrl(redirectUrl) + .build(); + StandardCheckoutPayResponse standardCheckoutResponse = + StandardCheckoutPayResponse.builder() + .orderId(String.valueOf(java.time.Instant.now().getEpochSecond())) + .state("PENDING") + .expireAt(java.time.Instant.now().getEpochSecond()) + .redirectUrl("https://google.com") + .build(); + Map headers = getHeaders(); + + addStubForPostRequest( + url, + headers, + standardCheckoutPayRequest, + HttpStatus.SC_OK, + Maps.newHashMap(), + standardCheckoutResponse); + StandardCheckoutPayResponse actual = standardCheckoutClient.pay(standardCheckoutPayRequest); + Assertions.assertEquals(standardCheckoutResponse, actual); + Assertions.assertNotNull(standardCheckoutPayRequest.getPrefillUserLoginDetails()); + Assertions.assertNull(standardCheckoutPayRequest.getPrefillUserLoginDetails().getPhoneNumber()); + } + + @SneakyThrows + @Test + void testStandardCheckoutPayWithoutPrefillUserLoginDetails() { + final String url = StandardCheckoutConstants.PAY_API; + String redirectUrl = "https://redirectUrl.com"; + + StandardCheckoutPayRequest standardCheckoutPayRequest = + StandardCheckoutPayRequest.builder() + .merchantOrderId(merchantOrderId) + .amount(amount) + .redirectUrl(redirectUrl) + .build(); + StandardCheckoutPayResponse standardCheckoutResponse = + StandardCheckoutPayResponse.builder() + .orderId(String.valueOf(java.time.Instant.now().getEpochSecond())) + .state("PENDING") + .expireAt(java.time.Instant.now().getEpochSecond()) + .redirectUrl("https://google.com") + .build(); + Map headers = getHeaders(); + + addStubForPostRequest( + url, + headers, + standardCheckoutPayRequest, + HttpStatus.SC_OK, + Maps.newHashMap(), + standardCheckoutResponse); + StandardCheckoutPayResponse actual = standardCheckoutClient.pay(standardCheckoutPayRequest); + Assertions.assertEquals(standardCheckoutResponse, actual); + Assertions.assertNull(standardCheckoutPayRequest.getPrefillUserLoginDetails()); + } } diff --git a/src/test/customCheckoutTests/CardPayRequestBuilderTest.java b/src/test/customCheckoutTests/CardPayRequestBuilderTest.java new file mode 100644 index 0000000..0a7fa05 --- /dev/null +++ b/src/test/customCheckoutTests/CardPayRequestBuilderTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package customCheckoutTests; + +import com.phonepe.sdk.pg.common.models.PgV2InstrumentType; +import com.phonepe.sdk.pg.common.models.PaymentFlowType; +import com.phonepe.sdk.pg.common.models.request.PgPaymentRequest; +import com.phonepe.sdk.pg.common.models.request.instruments.CardPaymentV2Instrument; +import com.phonepe.sdk.pg.payments.v2.models.request.PgPaymentFlow; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class CardPayRequestBuilderTest { + + private PgPaymentRequest buildFullCardRequest(String orderId) { + return PgPaymentRequest.CardPayRequestBuilder() + .merchantOrderId(orderId) + .amount(2000L) + .encryptionKeyId(99L) + .authMode("H2H") + .encryptedCardNumber("encCard9876") + .encryptedCvv("encCvv111") + .expiryMonth("06") + .expiryYear("2027") + .cardHolderName("Jane Smith") + .merchantUserId("USER_CARD_001") + .redirectUrl("https://merchant.example.com/card/redirect") + .expireAfter(900L) + .build(); + } + + @Test + void testBuildWithAllFields() { + PgPaymentRequest request = buildFullCardRequest("ORDER_CARD_001"); + + Assertions.assertNotNull(request); + Assertions.assertEquals("ORDER_CARD_001", request.getMerchantOrderId()); + Assertions.assertEquals(2000L, request.getAmount()); + } + + @Test + void testPaymentFlowIsSetToPg() { + PgPaymentRequest request = buildFullCardRequest("ORDER_CARD_002"); + + Assertions.assertNotNull(request.getPaymentFlow()); + Assertions.assertTrue(request.getPaymentFlow() instanceof PgPaymentFlow); + Assertions.assertEquals(PaymentFlowType.PG, request.getPaymentFlow().getType()); + } + + @Test + void testPaymentModeIsCard() { + PgPaymentRequest request = buildFullCardRequest("ORDER_CARD_003"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertTrue(flow.getPaymentMode() instanceof CardPaymentV2Instrument); + CardPaymentV2Instrument instrument = (CardPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(PgV2InstrumentType.CARD, instrument.getType()); + } + + @Test + void testCardDetailsFieldsAreCorrectlySet() { + PgPaymentRequest request = buildFullCardRequest("ORDER_CARD_004"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CardPaymentV2Instrument instrument = (CardPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertNotNull(instrument.getCardDetails()); + Assertions.assertEquals( + "encCard9876", instrument.getCardDetails().getEncryptedCardNumber()); + Assertions.assertEquals("encCvv111", instrument.getCardDetails().getEncryptedCvv()); + Assertions.assertEquals(99L, instrument.getCardDetails().getEncryptionKeyId()); + Assertions.assertEquals("Jane Smith", instrument.getCardDetails().getCardHolderName()); + } + + @Test + void testCardExpiryIsCorrectlySet() { + PgPaymentRequest request = buildFullCardRequest("ORDER_CARD_005"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CardPaymentV2Instrument instrument = (CardPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertNotNull(instrument.getCardDetails().getExpiry()); + Assertions.assertEquals("06", instrument.getCardDetails().getExpiry().getMonth()); + Assertions.assertEquals("2027", instrument.getCardDetails().getExpiry().getYear()); + } + + @Test + void testMerchantUrlsRedirectUrlIsCorrectlySet() { + PgPaymentRequest request = buildFullCardRequest("ORDER_CARD_006"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertNotNull(flow.getMerchantUrls()); + Assertions.assertEquals( + "https://merchant.example.com/card/redirect", + flow.getMerchantUrls().getRedirectUrl()); + } + + @Test + void testMerchantUserIdIsCorrectlySet() { + PgPaymentRequest request = buildFullCardRequest("ORDER_CARD_007"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CardPaymentV2Instrument instrument = (CardPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals("USER_CARD_001", instrument.getMerchantUserId()); + } + + @Test + void testAuthModeIsCorrectlySet() { + PgPaymentRequest request = buildFullCardRequest("ORDER_CARD_008"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CardPaymentV2Instrument instrument = (CardPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals("H2H", instrument.getAuthMode()); + } + + @Test + void testExpireAfterIsSetWhenProvided() { + PgPaymentRequest request = buildFullCardRequest("ORDER_CARD_009"); + + Assertions.assertEquals(900L, request.getExpireAfter()); + } +} diff --git a/src/test/customCheckoutTests/CustomCheckoutBaseSetup.java b/src/test/customCheckoutTests/CustomCheckoutBaseSetup.java new file mode 100644 index 0000000..0f091cf --- /dev/null +++ b/src/test/customCheckoutTests/CustomCheckoutBaseSetup.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package customCheckoutTests; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.MappingBuilder; +import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; +import com.google.common.collect.ImmutableMap; +import com.phonepe.sdk.pg.Env; +import com.phonepe.sdk.pg.common.constants.Headers; +import com.phonepe.sdk.pg.common.tokenhandler.OAuthResponse; +import com.phonepe.sdk.pg.common.tokenhandler.TokenConstants; +import com.phonepe.sdk.pg.payments.v2.CustomCheckoutClient; +import java.util.Map; +import okhttp3.FormBody; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; + +/** + * Base class for CustomCheckout tests. Sets up a WireMock server, stubs the OAuth token + * endpoint, and instantiates {@link CustomCheckoutClient} ready for use in tests. + */ +abstract class CustomCheckoutBaseSetup { + + protected static final int WIREMOCK_PORT = 30419; + + protected final ObjectMapper mapper = new ObjectMapper(); + + private static final WireMockConfiguration wireMockConfiguration = + new WireMockConfiguration().port(WIREMOCK_PORT); + protected static final WireMockServer wireMockServer = + new WireMockServer(wireMockConfiguration); + + protected CustomCheckoutClient customCheckoutClient; + + protected final String clientId = "CLIENTID"; + protected final String clientSecret = "CLIENTSECRET"; + protected final Integer clientVersion = 1; + protected final Env env = Env.TEST; + protected final String merchantOrderId = "Merchant_CC_001"; + protected final long amount = 100L; + + private final FormBody oauthFormBody = + new FormBody.Builder() + .add("client_id", clientId) + .add("client_secret", clientSecret) + .add("grant_type", "client_credentials") + .add("client_version", String.valueOf(clientVersion)) + .build(); + + @BeforeEach + void setUp() { + wireMockServer.start(); + stubOAuthToken(); + this.customCheckoutClient = + CustomCheckoutClient.getInstance(clientId, clientSecret, clientVersion, env); + } + + @AfterEach + void tearDown() { + Assertions.assertEquals(0, wireMockServer.findAllUnmatchedRequests().size()); + wireMockServer.stop(); + } + + // ── OAuth stub ──────────────────────────────────────────────────────── + + private void stubOAuthToken() { + OAuthResponse oAuthResponse = + OAuthResponse.builder() + .accessToken("accessToken") + .encryptedAccessToken("encryptedAccessToken") + .expiresAt(java.time.Instant.now().getEpochSecond() + 500) + .expiresIn(2432) + .issuedAt(java.time.Instant.now().getEpochSecond()) + .refreshToken("refreshToken") + .tokenType("O-Bearer") + .sessionExpiresAt(234543534) + .build(); + + String requestBody = formDataToString(oauthFormBody); + addStubForPostRequest( + TokenConstants.OAUTH_GET_TOKEN, + getAuthHeaders(), + requestBody, + 200, + ImmutableMap.of(), + oAuthResponse); + } + + // ── Header helpers ──────────────────────────────────────────────────── + + protected Map getHeaders() { + return ImmutableMap.builder() + .put(Headers.CONTENT_TYPE, APPLICATION_JSON) + .put(Headers.SOURCE, Headers.INTEGRATION) + .put(Headers.SOURCE_VERSION, Headers.API_VERSION) + .put(Headers.SOURCE_PLATFORM, Headers.SDK_TYPE) + .put(Headers.SOURCE_PLATFORM_VERSION, Headers.SDK_VERSION) + .put(Headers.OAUTH_AUTHORIZATION, "O-Bearer accessToken") + .build(); + } + + protected Map getAuthHeaders() { + return ImmutableMap.builder() + .put("Content-Type", "application/x-www-form-urlencoded") + .put("accept", "application/json") + .build(); + } + + // ── WireMock stub helpers ───────────────────────────────────────────── + + protected void addStubForGetRequest( + final String urlPath, final int status, final Object response) { + addStubForGetRequest( + urlPath, ImmutableMap.of(), ImmutableMap.of(), status, ImmutableMap.of(), response); + } + + protected void addStubForGetRequest( + final String urlPath, + final Map queryParams, + final Map requestHeaders, + final int status, + final Map responseHeaders, + final Object response) { + final MappingBuilder mappingBuilder = WireMock.get(WireMock.urlPathEqualTo(urlPath)); + requestHeaders.forEach( + (key, value) -> mappingBuilder.withHeader(key, WireMock.containing(value))); + queryParams.forEach( + (key, value) -> mappingBuilder.withQueryParam(key, WireMock.equalTo(value))); + + wireMockServer.stubFor( + mappingBuilder.willReturn(buildResponse(status, responseHeaders, response))); + } + + protected void addStubForPostRequest( + final String urlPath, final Object request, final int status, final Object response) { + addStubForPostRequest( + urlPath, ImmutableMap.of(), request, status, ImmutableMap.of(), response); + } + + protected void addStubForPostRequest( + final String urlPath, + final Map requestHeaders, + final Object request, + final int status, + final Map responseHeaders, + final Object response) { + try { + String body = + request instanceof String + ? (String) request + : mapper.writeValueAsString(request); + + final MappingBuilder mappingBuilder = + WireMock.post(WireMock.urlPathEqualTo(urlPath)) + .withRequestBody(WireMock.equalTo(body)); + + requestHeaders.forEach( + (key, value) -> mappingBuilder.withHeader(key, WireMock.containing(value))); + + wireMockServer.stubFor( + mappingBuilder.willReturn(buildResponse(status, responseHeaders, response))); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private ResponseDefinitionBuilder buildResponse( + int status, Map responseHeaders, Object response) { + try { + ResponseDefinitionBuilder builder = + WireMock.aResponse() + .withStatus(status) + .withBody( + response instanceof String + ? (String) response + : mapper.writeValueAsString(response)); + responseHeaders.forEach(builder::withHeader); + return builder; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private String formDataToString(FormBody formBody) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < formBody.size(); i++) { + if (i > 0) sb.append("&"); + sb.append(formBody.encodedName(i)).append("=").append(formBody.encodedValue(i)); + } + return sb.toString(); + } +} diff --git a/src/test/customCheckoutTests/CustomCheckoutClientTest.java b/src/test/customCheckoutTests/CustomCheckoutClientTest.java new file mode 100644 index 0000000..a2be690 --- /dev/null +++ b/src/test/customCheckoutTests/CustomCheckoutClientTest.java @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package customCheckoutTests; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.phonepe.sdk.pg.common.exception.PhonePeException; +import com.phonepe.sdk.pg.common.http.PhonePeResponse; +import com.phonepe.sdk.pg.common.models.request.PgPaymentRequest; +import com.phonepe.sdk.pg.common.models.request.RefundRequest; +import com.phonepe.sdk.pg.common.models.response.OrderStatusResponse; +import com.phonepe.sdk.pg.common.models.response.PgPaymentResponse; +import com.phonepe.sdk.pg.common.models.response.RefundResponse; +import com.phonepe.sdk.pg.common.models.response.RefundStatusResponse; +import com.phonepe.sdk.pg.payments.v2.customcheckout.CustomCheckoutConstants; +import java.util.Collections; +import java.util.Map; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import wiremock.org.apache.http.HttpStatus; + +class CustomCheckoutClientTest extends CustomCheckoutBaseSetup { + + // ─────────────────────────────────────────────────────────────────────── + // pay() – UPI Collect via VPA + // ─────────────────────────────────────────────────────────────────────── + + @Test + void testPayViaVpaSuccess() { + final String url = CustomCheckoutConstants.PAY_API; + + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_CC_VPA_001") + .amount(1000L) + .vpa("customer@upi") + .message("Payment for order") + .build(); + + PgPaymentResponse response = + PgPaymentResponse.builder() + .orderId("OMO001") + .state("PENDING") + .expireAt(java.time.Instant.now().getEpochSecond() + 600) + .build(); + + addStubForPostRequest( + url, getHeaders(), request, HttpStatus.SC_OK, Maps.newHashMap(), response); + + PgPaymentResponse actual = customCheckoutClient.pay(request); + Assertions.assertEquals(response, actual); + } + + @Test + void testPayViaVpaBadRequest() { + final String url = CustomCheckoutConstants.PAY_API; + + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_CC_VPA_002") + .amount(500L) + .vpa("invalid-vpa") + .build(); + + PhonePeResponse errorResponse = + PhonePeResponse.>builder() + .code("BAD_REQUEST") + .message("Invalid VPA") + .data(Collections.singletonMap("field", (Object)"vpa")) + .build(); + + addStubForPostRequest( + url, + getHeaders(), + request, + HttpStatus.SC_BAD_REQUEST, + Maps.newHashMap(), + errorResponse); + + PhonePeException exception = + assertThrows(PhonePeException.class, () -> customCheckoutClient.pay(request)); + Assertions.assertEquals(400, exception.getHttpStatusCode()); + Assertions.assertEquals("BAD_REQUEST", exception.getCode()); + } + + // ─────────────────────────────────────────────────────────────────────── + // pay() – UPI Collect via Phone Number + // ─────────────────────────────────────────────────────────────────────── + + @Test + void testPayViaPhoneNumberSuccess() { + final String url = CustomCheckoutConstants.PAY_API; + + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_CC_PHONE_001") + .amount(2000L) + .phoneNumber("9876543210") + .message("Pay for subscription") + .build(); + + PgPaymentResponse response = + PgPaymentResponse.builder() + .orderId("OMO002") + .state("PENDING") + .expireAt(java.time.Instant.now().getEpochSecond() + 600) + .build(); + + addStubForPostRequest( + url, getHeaders(), request, HttpStatus.SC_OK, Maps.newHashMap(), response); + + PgPaymentResponse actual = customCheckoutClient.pay(request); + Assertions.assertEquals(response, actual); + } + + @Test + void testPayViaPhoneNumberBadRequest() { + final String url = CustomCheckoutConstants.PAY_API; + + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_CC_PHONE_002") + .amount(500L) + .phoneNumber("000") + .build(); + + PhonePeResponse errorResponse = + PhonePeResponse.>builder() + .code("INVALID_PHONE_NUMBER") + .message("Phone number is not valid") + .data(Collections.emptyMap()) + .build(); + + addStubForPostRequest( + url, + getHeaders(), + request, + HttpStatus.SC_BAD_REQUEST, + Maps.newHashMap(), + errorResponse); + + PhonePeException exception = + assertThrows(PhonePeException.class, () -> customCheckoutClient.pay(request)); + Assertions.assertEquals(400, exception.getHttpStatusCode()); + Assertions.assertEquals("INVALID_PHONE_NUMBER", exception.getCode()); + } + + @Test + void testPayServerError() { + final String url = CustomCheckoutConstants.PAY_API; + + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_CC_VPA_SERVER_ERR") + .amount(1500L) + .vpa("user@upi") + .build(); + + PhonePeResponse errorResponse = + PhonePeResponse.>builder() + .code("INTERNAL_SERVER_ERROR") + .message("Service Unavailable") + .data(Collections.emptyMap()) + .build(); + + addStubForPostRequest( + url, + getHeaders(), + request, + HttpStatus.SC_INTERNAL_SERVER_ERROR, + Maps.newHashMap(), + errorResponse); + + PhonePeException exception = + assertThrows(PhonePeException.class, () -> customCheckoutClient.pay(request)); + Assertions.assertEquals(500, exception.getHttpStatusCode()); + } + + // ─────────────────────────────────────────────────────────────────────── + // getOrderStatus() + // ─────────────────────────────────────────────────────────────────────── + + @Test + void testGetOrderStatusSuccess() { + String url = + String.format(CustomCheckoutConstants.ORDER_STATUS_API, merchantOrderId); + OrderStatusResponse orderStatusResponse = + OrderStatusResponse.builder() + .orderId("OMO003") + .merchantOrderId(merchantOrderId) + .state("COMPLETED") + .amount(1000L) + .build(); + + addStubForGetRequest( + url, + ImmutableMap.of(CustomCheckoutConstants.ORDER_DETAILS, "false"), + getHeaders(), + HttpStatus.SC_OK, + ImmutableMap.of(), + orderStatusResponse); + + OrderStatusResponse actual = customCheckoutClient.getOrderStatus(merchantOrderId); + Assertions.assertEquals(orderStatusResponse, actual); + } + + @Test + void testGetOrderStatusWithDetailsFlagSuccess() { + String url = + String.format(CustomCheckoutConstants.ORDER_STATUS_API, merchantOrderId); + OrderStatusResponse orderStatusResponse = + OrderStatusResponse.builder() + .orderId("OMO004") + .merchantOrderId(merchantOrderId) + .state("PENDING") + .amount(2000L) + .build(); + + addStubForGetRequest( + url, + ImmutableMap.of(CustomCheckoutConstants.ORDER_DETAILS, "true"), + getHeaders(), + HttpStatus.SC_OK, + ImmutableMap.of(), + orderStatusResponse); + + OrderStatusResponse actual = customCheckoutClient.getOrderStatus(merchantOrderId, true); + Assertions.assertEquals(orderStatusResponse, actual); + } + + @Test + void testGetOrderStatusNotFound() { + String unknownOrderId = "UNKNOWN_ORDER"; + String url = String.format(CustomCheckoutConstants.ORDER_STATUS_API, unknownOrderId); + + PhonePeResponse errorResponse = + PhonePeResponse.>builder() + .code("ORDER_NOT_FOUND") + .message("Order not found") + .data(Collections.emptyMap()) + .build(); + + addStubForGetRequest( + url, + ImmutableMap.of(CustomCheckoutConstants.ORDER_DETAILS, "false"), + getHeaders(), + HttpStatus.SC_NOT_FOUND, + ImmutableMap.of(), + errorResponse); + + PhonePeException exception = + assertThrows( + PhonePeException.class, + () -> customCheckoutClient.getOrderStatus(unknownOrderId)); + Assertions.assertEquals(404, exception.getHttpStatusCode()); + Assertions.assertEquals("ORDER_NOT_FOUND", exception.getCode()); + } + + // ─────────────────────────────────────────────────────────────────────── + // getTransactionStatus() + // ─────────────────────────────────────────────────────────────────────── + + @Test + void testGetTransactionStatusSuccess() { + String transactionId = "TXN_CC_001"; + String url = String.format(CustomCheckoutConstants.TRANSACTION_STATUS_API, transactionId); + + OrderStatusResponse transactionStatusResponse = + OrderStatusResponse.builder() + .orderId("OMO005") + .state("COMPLETED") + .amount(3000L) + .build(); + + addStubForGetRequest( + url, + ImmutableMap.of(), + getHeaders(), + HttpStatus.SC_OK, + ImmutableMap.of(), + transactionStatusResponse); + + OrderStatusResponse actual = customCheckoutClient.getTransactionStatus(transactionId); + Assertions.assertEquals(transactionStatusResponse, actual); + } + + @Test + void testGetTransactionStatusNotFound() { + String transactionId = "TXN_INVALID"; + String url = String.format(CustomCheckoutConstants.TRANSACTION_STATUS_API, transactionId); + + PhonePeResponse errorResponse = + PhonePeResponse.>builder() + .code("TRANSACTION_NOT_FOUND") + .message("Transaction does not exist") + .data(Collections.emptyMap()) + .build(); + + addStubForGetRequest( + url, + ImmutableMap.of(), + getHeaders(), + HttpStatus.SC_NOT_FOUND, + ImmutableMap.of(), + errorResponse); + + PhonePeException exception = + assertThrows( + PhonePeException.class, + () -> customCheckoutClient.getTransactionStatus(transactionId)); + Assertions.assertEquals(404, exception.getHttpStatusCode()); + Assertions.assertEquals("TRANSACTION_NOT_FOUND", exception.getCode()); + } + + // ─────────────────────────────────────────────────────────────────────── + // refund() + // ─────────────────────────────────────────────────────────────────────── + + @Test + void testRefundSuccess() { + final String url = CustomCheckoutConstants.REFUND_API; + + RefundRequest refundRequest = + RefundRequest.builder() + .merchantRefundId("REFUND_CC_001") + .originalMerchantOrderId(merchantOrderId) + .amount(500L) + .build(); + + RefundResponse refundResponse = + RefundResponse.builder() + .refundId("REFUND_CC_001") + .state("CREATED") + .amount(500L) + .build(); + + addStubForPostRequest( + url, + getHeaders(), + refundRequest, + HttpStatus.SC_OK, + Maps.newHashMap(), + refundResponse); + + RefundResponse actual = customCheckoutClient.refund(refundRequest); + Assertions.assertEquals(refundResponse, actual); + } + + @Test + void testRefundBadRequest() { + final String url = CustomCheckoutConstants.REFUND_API; + + RefundRequest refundRequest = + RefundRequest.builder() + .merchantRefundId("REFUND_CC_002") + .originalMerchantOrderId("NONEXISTENT_ORDER") + .amount(9999999L) + .build(); + + PhonePeResponse errorResponse = + PhonePeResponse.>builder() + .code("REFUND_AMOUNT_EXCEEDS") + .message("Refund amount exceeds original order amount") + .data(Collections.emptyMap()) + .build(); + + addStubForPostRequest( + url, + getHeaders(), + refundRequest, + HttpStatus.SC_BAD_REQUEST, + Maps.newHashMap(), + errorResponse); + + PhonePeException exception = + assertThrows( + PhonePeException.class, () -> customCheckoutClient.refund(refundRequest)); + Assertions.assertEquals(400, exception.getHttpStatusCode()); + Assertions.assertEquals("REFUND_AMOUNT_EXCEEDS", exception.getCode()); + } + + // ─────────────────────────────────────────────────────────────────────── + // getRefundStatus() + // ─────────────────────────────────────────────────────────────────────── + + @Test + void testGetRefundStatusSuccess() { + String refundId = "REFUND_STATUS_CC_001"; + String url = String.format(CustomCheckoutConstants.REFUND_STATUS_API, refundId); + + RefundStatusResponse refundStatusResponse = + RefundStatusResponse.builder() + .merchantRefundId(refundId) + .originalMerchantOrderId(merchantOrderId) + .amount(500L) + .state("COMPLETED") + .paymentDetails(Collections.emptyList()) + .build(); + + addStubForGetRequest( + url, + ImmutableMap.of(), + getHeaders(), + HttpStatus.SC_OK, + ImmutableMap.of(), + refundStatusResponse); + + RefundStatusResponse actual = customCheckoutClient.getRefundStatus(refundId); + Assertions.assertEquals(refundStatusResponse, actual); + } + + @Test + void testGetRefundStatusNotFound() { + String refundId = "REFUND_UNKNOWN"; + String url = String.format(CustomCheckoutConstants.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, + () -> customCheckoutClient.getRefundStatus(refundId)); + Assertions.assertEquals(404, exception.getHttpStatusCode()); + Assertions.assertEquals("REFUND_NOT_FOUND", exception.getCode()); + } +} diff --git a/src/test/customCheckoutTests/NetBankingPayRequestBuilderTest.java b/src/test/customCheckoutTests/NetBankingPayRequestBuilderTest.java new file mode 100644 index 0000000..9576f80 --- /dev/null +++ b/src/test/customCheckoutTests/NetBankingPayRequestBuilderTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package customCheckoutTests; + +import com.phonepe.sdk.pg.common.models.PgV2InstrumentType; +import com.phonepe.sdk.pg.common.models.PaymentFlowType; +import com.phonepe.sdk.pg.common.models.request.PgPaymentRequest; +import com.phonepe.sdk.pg.common.models.request.instruments.NetBankingPaymentV2Instrument; +import com.phonepe.sdk.pg.payments.v2.models.request.PgPaymentFlow; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class NetBankingPayRequestBuilderTest { + + @Test + void testBuildWithAllFields() { + PgPaymentRequest request = + PgPaymentRequest.NetBankingPayRequestBuilder() + .merchantOrderId("ORDER_NB_001") + .amount(1000L) + .bankId("HDFC") + .merchantUserId("USER_001") + .redirectUrl("https://merchant.example.com/redirect") + .expireAfter(600L) + .build(); + + Assertions.assertNotNull(request); + Assertions.assertEquals("ORDER_NB_001", request.getMerchantOrderId()); + Assertions.assertEquals(1000L, request.getAmount()); + } + + @Test + void testPaymentFlowIsSetToPg() { + PgPaymentRequest request = + PgPaymentRequest.NetBankingPayRequestBuilder() + .merchantOrderId("ORDER_NB_002") + .amount(200L) + .bankId("ICICI") + .redirectUrl("https://merchant.example.com/redirect") + .build(); + + Assertions.assertNotNull(request.getPaymentFlow()); + Assertions.assertTrue(request.getPaymentFlow() instanceof PgPaymentFlow); + Assertions.assertEquals(PaymentFlowType.PG, request.getPaymentFlow().getType()); + } + + @Test + void testPaymentModeIsNetBanking() { + PgPaymentRequest request = + PgPaymentRequest.NetBankingPayRequestBuilder() + .merchantOrderId("ORDER_NB_003") + .amount(300L) + .bankId("SBI") + .redirectUrl("https://merchant.example.com/redirect") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertTrue(flow.getPaymentMode() instanceof NetBankingPaymentV2Instrument); + NetBankingPaymentV2Instrument instrument = + (NetBankingPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(PgV2InstrumentType.NET_BANKING, instrument.getType()); + } + + @Test + void testBankIdIsCorrectlySet() { + String expectedBankId = "AXIS"; + PgPaymentRequest request = + PgPaymentRequest.NetBankingPayRequestBuilder() + .merchantOrderId("ORDER_NB_004") + .amount(400L) + .bankId(expectedBankId) + .redirectUrl("https://merchant.example.com/redirect") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + NetBankingPaymentV2Instrument instrument = + (NetBankingPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(expectedBankId, instrument.getBankId()); + } + + @Test + void testMerchantUserIdIsCorrectlySet() { + String expectedUserId = "USER_NB_123"; + PgPaymentRequest request = + PgPaymentRequest.NetBankingPayRequestBuilder() + .merchantOrderId("ORDER_NB_005") + .amount(500L) + .bankId("HDFC") + .merchantUserId(expectedUserId) + .redirectUrl("https://merchant.example.com/redirect") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + NetBankingPaymentV2Instrument instrument = + (NetBankingPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(expectedUserId, instrument.getMerchantUserId()); + } + + @Test + void testMerchantUrlsRedirectUrlIsCorrectlySet() { + String expectedRedirectUrl = "https://merchant.example.com/nb/callback"; + PgPaymentRequest request = + PgPaymentRequest.NetBankingPayRequestBuilder() + .merchantOrderId("ORDER_NB_006") + .amount(600L) + .bankId("KOTAK") + .redirectUrl(expectedRedirectUrl) + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertNotNull(flow.getMerchantUrls()); + Assertions.assertEquals(expectedRedirectUrl, flow.getMerchantUrls().getRedirectUrl()); + } + + @Test + void testExpireAfterIsSetWhenProvided() { + long expireAfter = 900L; + PgPaymentRequest request = + PgPaymentRequest.NetBankingPayRequestBuilder() + .merchantOrderId("ORDER_NB_007") + .amount(700L) + .bankId("PNB") + .redirectUrl("https://merchant.example.com/redirect") + .expireAfter(expireAfter) + .build(); + + Assertions.assertEquals(expireAfter, request.getExpireAfter()); + } + + @Test + void testExpireAfterIsNullWhenNotProvided() { + PgPaymentRequest request = + PgPaymentRequest.NetBankingPayRequestBuilder() + .merchantOrderId("ORDER_NB_008") + .amount(800L) + .bankId("BOB") + .redirectUrl("https://merchant.example.com/redirect") + .build(); + + Assertions.assertNull(request.getExpireAfter()); + } +} diff --git a/src/test/customCheckoutTests/TokenPayRequestBuilderTest.java b/src/test/customCheckoutTests/TokenPayRequestBuilderTest.java new file mode 100644 index 0000000..cfb88b5 --- /dev/null +++ b/src/test/customCheckoutTests/TokenPayRequestBuilderTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package customCheckoutTests; + +import com.phonepe.sdk.pg.common.models.PgV2InstrumentType; +import com.phonepe.sdk.pg.common.models.PaymentFlowType; +import com.phonepe.sdk.pg.common.models.request.PgPaymentRequest; +import com.phonepe.sdk.pg.common.models.request.instruments.TokenPaymentV2Instrument; +import com.phonepe.sdk.pg.payments.v2.models.request.PgPaymentFlow; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class TokenPayRequestBuilderTest { + + private PgPaymentRequest buildFullTokenRequest(String orderId) { + return PgPaymentRequest.TokenPayRequestBuilder() + .merchantOrderId(orderId) + .amount(1000L) + .encryptionKeyId(42L) + .authMode("H2H") + .encryptedToken("encTok123") + .encryptedCvv("encCvv456") + .cryptogram("crypto789") + .panSuffix("1234") + .expiryMonth("12") + .expiryYear("2028") + .redirectUrl("https://merchant.example.com/redirect") + .cardHolderName("John Doe") + .merchantUserId("USER_TOKEN_001") + .expireAfter(600L) + .build(); + } + + @Test + void testBuildWithAllFields() { + PgPaymentRequest request = buildFullTokenRequest("ORDER_TOKEN_001"); + + Assertions.assertNotNull(request); + Assertions.assertEquals("ORDER_TOKEN_001", request.getMerchantOrderId()); + Assertions.assertEquals(1000L, request.getAmount()); + } + + @Test + void testPaymentFlowIsSetToPg() { + PgPaymentRequest request = buildFullTokenRequest("ORDER_TOKEN_002"); + + Assertions.assertNotNull(request.getPaymentFlow()); + Assertions.assertTrue(request.getPaymentFlow() instanceof PgPaymentFlow); + Assertions.assertEquals(PaymentFlowType.PG, request.getPaymentFlow().getType()); + } + + @Test + void testPaymentModeIsToken() { + PgPaymentRequest request = buildFullTokenRequest("ORDER_TOKEN_003"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertTrue(flow.getPaymentMode() instanceof TokenPaymentV2Instrument); + TokenPaymentV2Instrument instrument = (TokenPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(PgV2InstrumentType.TOKEN, instrument.getType()); + } + + @Test + void testTokenDetailsFieldsAreCorrectlySet() { + PgPaymentRequest request = buildFullTokenRequest("ORDER_TOKEN_004"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + TokenPaymentV2Instrument instrument = (TokenPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertNotNull(instrument.getTokenDetails()); + Assertions.assertEquals("encTok123", instrument.getTokenDetails().getEncryptedToken()); + Assertions.assertEquals("encCvv456", instrument.getTokenDetails().getEncryptedCvv()); + Assertions.assertEquals(42L, instrument.getTokenDetails().getEncryptionKeyId()); + Assertions.assertEquals("crypto789", instrument.getTokenDetails().getCryptogram()); + Assertions.assertEquals("1234", instrument.getTokenDetails().getPanSuffix()); + Assertions.assertEquals("John Doe", instrument.getTokenDetails().getCardHolderName()); + } + + @Test + void testExpiryMonthAndYearAreCorrectlySet() { + PgPaymentRequest request = buildFullTokenRequest("ORDER_TOKEN_005"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + TokenPaymentV2Instrument instrument = (TokenPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertNotNull(instrument.getTokenDetails().getExpiry()); + Assertions.assertEquals("12", instrument.getTokenDetails().getExpiry().getMonth()); + Assertions.assertEquals("2028", instrument.getTokenDetails().getExpiry().getYear()); + } + + @Test + void testMerchantUrlsRedirectUrlIsCorrectlySet() { + PgPaymentRequest request = buildFullTokenRequest("ORDER_TOKEN_006"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertNotNull(flow.getMerchantUrls()); + Assertions.assertEquals( + "https://merchant.example.com/redirect", + flow.getMerchantUrls().getRedirectUrl()); + } + + @Test + void testMerchantUserIdIsCorrectlySet() { + PgPaymentRequest request = buildFullTokenRequest("ORDER_TOKEN_007"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + TokenPaymentV2Instrument instrument = (TokenPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals("USER_TOKEN_001", instrument.getMerchantUserId()); + } + + @Test + void testAuthModeIsCorrectlySet() { + PgPaymentRequest request = buildFullTokenRequest("ORDER_TOKEN_008"); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + TokenPaymentV2Instrument instrument = (TokenPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals("H2H", instrument.getAuthMode()); + } + + @Test + void testExpireAfterIsSetWhenProvided() { + PgPaymentRequest request = buildFullTokenRequest("ORDER_TOKEN_009"); + + Assertions.assertEquals(600L, request.getExpireAfter()); + } +} diff --git a/src/test/customCheckoutTests/UpiCollectPayViaPhoneNumberRequestBuilderTest.java b/src/test/customCheckoutTests/UpiCollectPayViaPhoneNumberRequestBuilderTest.java new file mode 100644 index 0000000..06e72c3 --- /dev/null +++ b/src/test/customCheckoutTests/UpiCollectPayViaPhoneNumberRequestBuilderTest.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package customCheckoutTests; + +import com.phonepe.sdk.pg.common.models.PgV2InstrumentType; +import com.phonepe.sdk.pg.common.models.PaymentFlowType; +import com.phonepe.sdk.pg.common.models.request.PgPaymentRequest; +import com.phonepe.sdk.pg.common.models.request.instruments.CollectPaymentV2Instrument; +import com.phonepe.sdk.pg.common.models.request.instruments.PhoneNumberCollectPaymentDetails; +import com.phonepe.sdk.pg.payments.v2.models.request.PgPaymentFlow; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class UpiCollectPayViaPhoneNumberRequestBuilderTest { + + @Test + void testBuildWithRequiredFieldsOnly() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_001") + .amount(100L) + .phoneNumber("9876543210") + .build(); + + Assertions.assertNotNull(request); + Assertions.assertEquals("ORDER_PHONE_001", request.getMerchantOrderId()); + Assertions.assertEquals(100L, request.getAmount()); + Assertions.assertNull(request.getMetaInfo()); + Assertions.assertNull(request.getExpireAfter()); + } + + @Test + void testPaymentFlowIsSetToPg() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_002") + .amount(200L) + .phoneNumber("9000000001") + .build(); + + Assertions.assertNotNull(request.getPaymentFlow()); + Assertions.assertTrue(request.getPaymentFlow() instanceof PgPaymentFlow); + Assertions.assertEquals(PaymentFlowType.PG, request.getPaymentFlow().getType()); + } + + @Test + void testPaymentModeIsUpiCollect() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_003") + .amount(300L) + .phoneNumber("9000000002") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertTrue(flow.getPaymentMode() instanceof CollectPaymentV2Instrument); + CollectPaymentV2Instrument instrument = (CollectPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(PgV2InstrumentType.UPI_COLLECT, instrument.getType()); + } + + @Test + void testPhoneNumberIsCorrectlySet() { + String expectedPhone = "9123456789"; + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_004") + .amount(400L) + .phoneNumber(expectedPhone) + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CollectPaymentV2Instrument instrument = (CollectPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertTrue( + instrument.getDetails() instanceof PhoneNumberCollectPaymentDetails); + PhoneNumberCollectPaymentDetails details = + (PhoneNumberCollectPaymentDetails) instrument.getDetails(); + Assertions.assertEquals(expectedPhone, details.getPhoneNumber()); + } + + @Test + void testMessageIsSetWhenProvided() { + String expectedMessage = "Collect for order #5678"; + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_005") + .amount(500L) + .phoneNumber("9111111111") + .message(expectedMessage) + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CollectPaymentV2Instrument instrument = (CollectPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(expectedMessage, instrument.getMessage()); + } + + @Test + void testMessageIsNullWhenNotProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_006") + .amount(600L) + .phoneNumber("9222222222") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CollectPaymentV2Instrument instrument = (CollectPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertNull(instrument.getMessage()); + } + + @Test + void testExpireAfterIsSetWhenProvided() { + long expireAfter = 900L; + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_007") + .amount(700L) + .phoneNumber("9333333333") + .expireAfter(expireAfter) + .build(); + + Assertions.assertEquals(expireAfter, request.getExpireAfter()); + } + + @Test + void testNoMerchantUrlsForUpiCollectPhoneNumber() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_008") + .amount(800L) + .phoneNumber("9444444444") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertNull(flow.getMerchantUrls()); + } + + @Test + void testConstraintsAreNullWhenNotProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_009") + .amount(900L) + .phoneNumber("9555555555") + .build(); + + Assertions.assertNull(request.getConstraints()); + } + + @Test + void testDetailsTypeIsPhoneNumber() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_010") + .amount(1000L) + .phoneNumber("9666666666") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CollectPaymentV2Instrument instrument = (CollectPaymentV2Instrument) flow.getPaymentMode(); + PhoneNumberCollectPaymentDetails details = + (PhoneNumberCollectPaymentDetails) instrument.getDetails(); + Assertions.assertEquals( + com.phonepe.sdk.pg.common.models.request.instruments.CollectPaymentDetailsType + .PHONE_NUMBER, + details.getType()); + } + + @Test + void testXDeviceOsIsSetWhenProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_011") + .amount(1100L) + .phoneNumber("9777777777") + .xDeviceOs("ANDROID") + .build(); + + Assertions.assertEquals("ANDROID", request.getXDeviceOs()); + } + + @Test + void testXDeviceOsIsNullWhenNotProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_012") + .amount(1200L) + .phoneNumber("9888888888") + .build(); + + Assertions.assertNull(request.getXDeviceOs()); + } + + @Test + void testXDeviceOsNotInJsonSerialization() throws Exception { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaPhoneNumberRequestBuilder() + .merchantOrderId("ORDER_PHONE_013") + .amount(1300L) + .phoneNumber("9999999999") + .xDeviceOs("IOS") + .build(); + + String json = new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(request); + Assertions.assertFalse(json.contains("xDeviceOs")); + } +} diff --git a/src/test/customCheckoutTests/UpiCollectPayViaVpaRequestBuilderTest.java b/src/test/customCheckoutTests/UpiCollectPayViaVpaRequestBuilderTest.java new file mode 100644 index 0000000..bc99c40 --- /dev/null +++ b/src/test/customCheckoutTests/UpiCollectPayViaVpaRequestBuilderTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package customCheckoutTests; + +import com.phonepe.sdk.pg.common.models.PgV2InstrumentType; +import com.phonepe.sdk.pg.common.models.PaymentFlowType; +import com.phonepe.sdk.pg.common.models.request.PgPaymentRequest; +import com.phonepe.sdk.pg.common.models.request.instruments.CollectPaymentV2Instrument; +import com.phonepe.sdk.pg.common.models.request.instruments.VpaCollectPaymentDetails; +import com.phonepe.sdk.pg.payments.v2.models.request.PgPaymentFlow; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class UpiCollectPayViaVpaRequestBuilderTest { + + @Test + void testBuildWithRequiredFieldsOnly() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_001") + .amount(100L) + .vpa("test@upi") + .build(); + + Assertions.assertNotNull(request); + Assertions.assertEquals("ORDER_VPA_001", request.getMerchantOrderId()); + Assertions.assertEquals(100L, request.getAmount()); + Assertions.assertNull(request.getMetaInfo()); + Assertions.assertNull(request.getExpireAfter()); + } + + @Test + void testPaymentFlowIsSetToPg() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_002") + .amount(200L) + .vpa("merchant@upi") + .build(); + + Assertions.assertNotNull(request.getPaymentFlow()); + Assertions.assertTrue(request.getPaymentFlow() instanceof PgPaymentFlow); + Assertions.assertEquals(PaymentFlowType.PG, request.getPaymentFlow().getType()); + } + + @Test + void testPaymentModeIsUpiCollect() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_003") + .amount(300L) + .vpa("user@paytm") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertTrue(flow.getPaymentMode() instanceof CollectPaymentV2Instrument); + CollectPaymentV2Instrument instrument = (CollectPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(PgV2InstrumentType.UPI_COLLECT, instrument.getType()); + } + + @Test + void testVpaIsCorrectlySet() { + String expectedVpa = "john.doe@hdfc"; + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_004") + .amount(400L) + .vpa(expectedVpa) + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CollectPaymentV2Instrument instrument = (CollectPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertTrue(instrument.getDetails() instanceof VpaCollectPaymentDetails); + VpaCollectPaymentDetails details = (VpaCollectPaymentDetails) instrument.getDetails(); + Assertions.assertEquals(expectedVpa, details.getVpa()); + } + + @Test + void testMessageIsSetWhenProvided() { + String expectedMessage = "Payment for invoice #1234"; + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_005") + .amount(500L) + .vpa("customer@sbi") + .message(expectedMessage) + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CollectPaymentV2Instrument instrument = (CollectPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(expectedMessage, instrument.getMessage()); + } + + @Test + void testMessageIsNullWhenNotProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_006") + .amount(600L) + .vpa("user@icici") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + CollectPaymentV2Instrument instrument = (CollectPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertNull(instrument.getMessage()); + } + + @Test + void testExpireAfterIsSetWhenProvided() { + long expireAfter = 600L; + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_007") + .amount(700L) + .vpa("user@axis") + .expireAfter(expireAfter) + .build(); + + Assertions.assertEquals(expireAfter, request.getExpireAfter()); + } + + @Test + void testNoMerchantUrlsForUpiCollectVpa() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_008") + .amount(800L) + .vpa("user@ybl") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertNull(flow.getMerchantUrls()); + } + + @Test + void testConstraintsAreNullWhenNotProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_009") + .amount(900L) + .vpa("user@oksbi") + .build(); + + Assertions.assertNull(request.getConstraints()); + } + + @Test + void testXDeviceOsIsSetWhenProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_010") + .amount(1000L) + .vpa("user@upi") + .xDeviceOs("ANDROID") + .build(); + + Assertions.assertEquals("ANDROID", request.getXDeviceOs()); + } + + @Test + void testXDeviceOsIsNullWhenNotProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_011") + .amount(1100L) + .vpa("user@upi") + .build(); + + Assertions.assertNull(request.getXDeviceOs()); + } + + @Test + void testXDeviceOsNotInJsonSerialization() throws Exception { + PgPaymentRequest request = + PgPaymentRequest.UpiCollectPayViaVpaRequestBuilder() + .merchantOrderId("ORDER_VPA_012") + .amount(1200L) + .vpa("user@upi") + .xDeviceOs("IOS") + .build(); + + String json = new com.fasterxml.jackson.databind.ObjectMapper().writeValueAsString(request); + Assertions.assertFalse(json.contains("xDeviceOs")); + } +} diff --git a/src/test/customCheckoutTests/UpiIntentPayRequestBuilderTest.java b/src/test/customCheckoutTests/UpiIntentPayRequestBuilderTest.java new file mode 100644 index 0000000..f355aee --- /dev/null +++ b/src/test/customCheckoutTests/UpiIntentPayRequestBuilderTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package customCheckoutTests; + +import com.phonepe.sdk.pg.common.models.PgV2InstrumentType; +import com.phonepe.sdk.pg.common.models.PaymentFlowType; +import com.phonepe.sdk.pg.common.models.request.DeviceContext; +import com.phonepe.sdk.pg.common.models.request.PgPaymentRequest; +import com.phonepe.sdk.pg.common.models.request.instruments.IntentPaymentV2Instrument; +import com.phonepe.sdk.pg.payments.v2.models.request.PgPaymentFlow; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class UpiIntentPayRequestBuilderTest { + + @Test + void testBuildWithRequiredFieldsOnly() { + PgPaymentRequest request = + PgPaymentRequest.UpiIntentPayRequestBuilder() + .merchantOrderId("ORDER_INTENT_001") + .amount(100L) + .targetApp("com.phonepe.app") + .build(); + + Assertions.assertNotNull(request); + Assertions.assertEquals("ORDER_INTENT_001", request.getMerchantOrderId()); + Assertions.assertEquals(100L, request.getAmount()); + Assertions.assertNull(request.getMetaInfo()); + Assertions.assertNull(request.getExpireAfter()); + } + + @Test + void testPaymentFlowIsSetToPg() { + PgPaymentRequest request = + PgPaymentRequest.UpiIntentPayRequestBuilder() + .merchantOrderId("ORDER_INTENT_002") + .amount(200L) + .targetApp("com.phonepe.app") + .build(); + + Assertions.assertNotNull(request.getPaymentFlow()); + Assertions.assertTrue(request.getPaymentFlow() instanceof PgPaymentFlow); + Assertions.assertEquals(PaymentFlowType.PG, request.getPaymentFlow().getType()); + } + + @Test + void testPaymentModeIsUpiIntent() { + PgPaymentRequest request = + PgPaymentRequest.UpiIntentPayRequestBuilder() + .merchantOrderId("ORDER_INTENT_003") + .amount(300L) + .targetApp("com.google.android.apps.nbu.paisa.user") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertTrue(flow.getPaymentMode() instanceof IntentPaymentV2Instrument); + IntentPaymentV2Instrument instrument = (IntentPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(PgV2InstrumentType.UPI_INTENT, instrument.getType()); + } + + @Test + void testTargetAppIsCorrectlySet() { + String expectedTargetApp = "net.one97.paytm"; + PgPaymentRequest request = + PgPaymentRequest.UpiIntentPayRequestBuilder() + .merchantOrderId("ORDER_INTENT_004") + .amount(400L) + .targetApp(expectedTargetApp) + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + IntentPaymentV2Instrument instrument = (IntentPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(expectedTargetApp, instrument.getTargetApp()); + } + + @Test + void testDeviceContextIsSetWhenDeviceOsAndCallBackSchemeProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiIntentPayRequestBuilder() + .merchantOrderId("ORDER_INTENT_005") + .amount(500L) + .targetApp("com.phonepe.app") + .deviceOS("ANDROID") + .merchantCallBackScheme("merchantapp://callback") + .build(); + + Assertions.assertNotNull(request.getDeviceContext()); + Assertions.assertEquals("ANDROID", request.getDeviceContext().getDeviceOS()); + Assertions.assertEquals( + "merchantapp://callback", + request.getDeviceContext().getMerchantCallBackScheme()); + } + + @Test + void testDeviceContextDeviceOsIsNullWhenNotProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiIntentPayRequestBuilder() + .merchantOrderId("ORDER_INTENT_006") + .amount(600L) + .targetApp("com.phonepe.app") + .build(); + + DeviceContext deviceContext = request.getDeviceContext(); + Assertions.assertNotNull(deviceContext); + Assertions.assertNull(deviceContext.getDeviceOS()); + } + + @Test + void testExpireAfterIsSetWhenProvided() { + long expireAfter = 600L; + PgPaymentRequest request = + PgPaymentRequest.UpiIntentPayRequestBuilder() + .merchantOrderId("ORDER_INTENT_007") + .amount(700L) + .targetApp("com.phonepe.app") + .expireAfter(expireAfter) + .build(); + + Assertions.assertEquals(expireAfter, request.getExpireAfter()); + } + + @Test + void testNoMerchantUrlsForUpiIntent() { + PgPaymentRequest request = + PgPaymentRequest.UpiIntentPayRequestBuilder() + .merchantOrderId("ORDER_INTENT_008") + .amount(800L) + .targetApp("com.phonepe.app") + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertNull(flow.getMerchantUrls()); + } + + @Test + void testConstraintsAreNullWhenNotProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiIntentPayRequestBuilder() + .merchantOrderId("ORDER_INTENT_009") + .amount(900L) + .targetApp("com.phonepe.app") + .build(); + + Assertions.assertNull(request.getConstraints()); + } +} diff --git a/src/test/customCheckoutTests/UpiQrRequestBuilderTest.java b/src/test/customCheckoutTests/UpiQrRequestBuilderTest.java new file mode 100644 index 0000000..b7d79e9 --- /dev/null +++ b/src/test/customCheckoutTests/UpiQrRequestBuilderTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package customCheckoutTests; + +import com.phonepe.sdk.pg.common.models.PgV2InstrumentType; +import com.phonepe.sdk.pg.common.models.PaymentFlowType; +import com.phonepe.sdk.pg.common.models.request.PgPaymentRequest; +import com.phonepe.sdk.pg.common.models.request.instruments.UpiQrPaymentV2Instrument; +import com.phonepe.sdk.pg.payments.v2.models.request.PgPaymentFlow; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class UpiQrRequestBuilderTest { + + @Test + void testBuildWithRequiredFieldsOnly() { + PgPaymentRequest request = + PgPaymentRequest.UpiQrRequestBuilder() + .merchantOrderId("ORDER_QR_001") + .amount(100L) + .build(); + + Assertions.assertNotNull(request); + Assertions.assertEquals("ORDER_QR_001", request.getMerchantOrderId()); + Assertions.assertEquals(100L, request.getAmount()); + Assertions.assertNull(request.getMetaInfo()); + Assertions.assertNull(request.getExpireAfter()); + } + + @Test + void testPaymentFlowIsSetToPg() { + PgPaymentRequest request = + PgPaymentRequest.UpiQrRequestBuilder() + .merchantOrderId("ORDER_QR_002") + .amount(200L) + .build(); + + Assertions.assertNotNull(request.getPaymentFlow()); + Assertions.assertTrue(request.getPaymentFlow() instanceof PgPaymentFlow); + Assertions.assertEquals(PaymentFlowType.PG, request.getPaymentFlow().getType()); + } + + @Test + void testPaymentModeIsUpiQr() { + PgPaymentRequest request = + PgPaymentRequest.UpiQrRequestBuilder() + .merchantOrderId("ORDER_QR_003") + .amount(300L) + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertTrue(flow.getPaymentMode() instanceof UpiQrPaymentV2Instrument); + UpiQrPaymentV2Instrument instrument = (UpiQrPaymentV2Instrument) flow.getPaymentMode(); + Assertions.assertEquals(PgV2InstrumentType.UPI_QR, instrument.getType()); + } + + @Test + void testNoMerchantUrlsForUpiQr() { + PgPaymentRequest request = + PgPaymentRequest.UpiQrRequestBuilder() + .merchantOrderId("ORDER_QR_004") + .amount(400L) + .build(); + + PgPaymentFlow flow = (PgPaymentFlow) request.getPaymentFlow(); + Assertions.assertNull(flow.getMerchantUrls()); + } + + @Test + void testNoDeviceContextForUpiQr() { + PgPaymentRequest request = + PgPaymentRequest.UpiQrRequestBuilder() + .merchantOrderId("ORDER_QR_005") + .amount(500L) + .build(); + + Assertions.assertNull(request.getDeviceContext()); + } + + @Test + void testExpireAfterIsSetWhenProvided() { + long expireAfter = 300L; + PgPaymentRequest request = + PgPaymentRequest.UpiQrRequestBuilder() + .merchantOrderId("ORDER_QR_006") + .amount(600L) + .expireAfter(expireAfter) + .build(); + + Assertions.assertEquals(expireAfter, request.getExpireAfter()); + } + + @Test + void testConstraintsAreNullWhenNotProvided() { + PgPaymentRequest request = + PgPaymentRequest.UpiQrRequestBuilder() + .merchantOrderId("ORDER_QR_007") + .amount(700L) + .build(); + + Assertions.assertNull(request.getConstraints()); + } +} diff --git a/src/test/standardCheckoutTests/PrefillUserLoginDetailsTest.java b/src/test/standardCheckoutTests/PrefillUserLoginDetailsTest.java new file mode 100644 index 0000000..a51394a --- /dev/null +++ b/src/test/standardCheckoutTests/PrefillUserLoginDetailsTest.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2025 Original Author(s), PhonePe India Pvt. Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package standardCheckoutTests; +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.phonepe.sdk.pg.payments.v2.models.request.PrefillUserLoginDetails; +import com.phonepe.sdk.pg.payments.v2.models.request.StandardCheckoutPayRequest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; + +/** + * Unit tests for PrefillUserLoginDetails feature. + * Tests cover: + * - PrefillUserLoginDetails class functionality + * - Integration with StandardCheckoutPayRequest + * - JSON serialization/deserialization + * - Edge cases and null handling + */ +class PrefillUserLoginDetailsTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + private final String redirectUrl = "https://merchant.com/callback"; + private final String merchantOrderId = "ORDER_" + System.currentTimeMillis(); + private final long amount = 10000L; // 100 INR + + // ========== PrefillUserLoginDetails Class Tests ========== + + @Test + @DisplayName("Should create PrefillUserLoginDetails with valid phone number using builder") + void testCreatePrefillUserLoginDetailsWithValidPhoneNumber() { + // Arrange & Act + PrefillUserLoginDetails prefill = PrefillUserLoginDetails.builder() + .phoneNumber("9876543210") + .build(); + + // Assert + assertNotNull(prefill); + assertEquals("9876543210", prefill.getPhoneNumber()); + } + + @Test + @DisplayName("Should create PrefillUserLoginDetails with null phone number") + void testCreatePrefillUserLoginDetailsWithNullPhoneNumber() { + // Arrange & Act + PrefillUserLoginDetails prefill = PrefillUserLoginDetails.builder() + .phoneNumber(null) + .build(); + + // Assert + assertNotNull(prefill); + assertNull(prefill.getPhoneNumber()); + } + + // ========== JSON Serialization Tests ========== + + @Test + @DisplayName("Should serialize PrefillUserLoginDetails to JSON with phone number") + void testSerializePrefillUserLoginDetailsWithPhoneNumber() throws Exception { + // Arrange + PrefillUserLoginDetails prefill = PrefillUserLoginDetails.builder() + .phoneNumber("9876543210") + .build(); + + // Act + String json = objectMapper.writeValueAsString(prefill); + + // Assert + assertNotNull(json); + assertTrue(json.contains("phoneNumber")); + assertTrue(json.contains("9876543210")); + assertEquals("{\"phoneNumber\":\"9876543210\"}", json); + } + + // ========== StandardCheckoutPayRequest Integration Tests ========== + + @Test + @DisplayName("Should create StandardCheckoutPayRequest with prefillUserLoginDetails") + void testStandardCheckoutPayRequestWithPrefillUserLoginDetails() { + // Arrange + PrefillUserLoginDetails prefill = PrefillUserLoginDetails.builder() + .phoneNumber("9876543210") + .build(); + + // Act + StandardCheckoutPayRequest request = StandardCheckoutPayRequest.builder() + .merchantOrderId(merchantOrderId) + .amount(amount) + .redirectUrl(redirectUrl) + .prefillUserLoginDetails(prefill) + .build(); + + // Assert + assertNotNull(request); + assertEquals(merchantOrderId, request.getMerchantOrderId()); + assertEquals(amount, request.getAmount()); + assertNotNull(request.getPrefillUserLoginDetails()); + assertEquals("9876543210", request.getPrefillUserLoginDetails().getPhoneNumber()); + } + + @Test + @DisplayName("Should create StandardCheckoutPayRequest without prefillUserLoginDetails") + void testStandardCheckoutPayRequestWithoutPrefillUserLoginDetails() { + // Arrange & Act + StandardCheckoutPayRequest request = StandardCheckoutPayRequest.builder() + .merchantOrderId(merchantOrderId) + .amount(amount) + .redirectUrl(redirectUrl) + .build(); + + // Assert + assertNotNull(request); + assertEquals(merchantOrderId, request.getMerchantOrderId()); + assertEquals(amount, request.getAmount()); + assertNull(request.getPrefillUserLoginDetails()); + } + + @Test + @DisplayName("Should create StandardCheckoutPayRequest with null prefillUserLoginDetails") + void testStandardCheckoutPayRequestWithNullPrefillUserLoginDetails() { + // Arrange & Act + StandardCheckoutPayRequest request = StandardCheckoutPayRequest.builder() + .merchantOrderId(merchantOrderId) + .amount(amount) + .redirectUrl(redirectUrl) + .prefillUserLoginDetails(null) + .build(); + + // Assert + assertNotNull(request); + assertNull(request.getPrefillUserLoginDetails()); + } + + @Test + @DisplayName("Should serialize StandardCheckoutPayRequest with prefillUserLoginDetails to JSON") + void testSerializeStandardCheckoutPayRequestWithPrefillUserLoginDetails() throws Exception { + // Arrange + PrefillUserLoginDetails prefill = PrefillUserLoginDetails.builder() + .phoneNumber("9876543210") + .build(); + + StandardCheckoutPayRequest request = StandardCheckoutPayRequest.builder() + .merchantOrderId(merchantOrderId) + .amount(amount) + .redirectUrl(redirectUrl) + .prefillUserLoginDetails(prefill) + .build(); + + // Act + String json = objectMapper.writeValueAsString(request); + + // Assert + assertNotNull(json); + assertTrue(json.contains("prefillUserLoginDetails")); + assertTrue(json.contains("phoneNumber")); + assertTrue(json.contains("9876543210")); + } + + // ========== Edge Cases and Validation Tests ========== + + @Test + @DisplayName("Should handle empty string phone number") + void testPrefillUserLoginDetailsWithEmptyStringPhoneNumber() { + // Arrange & Act + PrefillUserLoginDetails prefill = PrefillUserLoginDetails.builder() + .phoneNumber("") + .build(); + + // Assert + assertNotNull(prefill); + assertEquals("", prefill.getPhoneNumber()); + } + + // ========== Backward Compatibility Tests ========== + + @Test + @DisplayName("Should maintain backward compatibility - request without prefillUserLoginDetails works") + void testBackwardCompatibilityWithoutPrefillUserLoginDetails() { + // This test ensures existing code continues to work + // Act + StandardCheckoutPayRequest request = StandardCheckoutPayRequest.builder() + .merchantOrderId(merchantOrderId) + .amount(amount) + .redirectUrl(redirectUrl) + .message("Test payment") + .disablePaymentRetry(false) + .build(); + + // Assert + assertNotNull(request); + assertEquals(merchantOrderId, request.getMerchantOrderId()); + assertEquals(amount, request.getAmount()); + assertNull(request.getPrefillUserLoginDetails()); + assertNotNull(request.getPaymentFlow()); + } + + @Test + @DisplayName("Should work with all other StandardCheckoutPayRequest parameters") + void testPrefillUserLoginDetailsWithAllOtherParameters() { + // Arrange + PrefillUserLoginDetails prefill = PrefillUserLoginDetails.builder() + .phoneNumber("9876543210") + .build(); + + // Act + StandardCheckoutPayRequest request = StandardCheckoutPayRequest.builder() + .merchantOrderId(merchantOrderId) + .amount(amount) + .redirectUrl(redirectUrl) + .message("Test payment message") + .expireAfter(3600L) // 1 hour + .disablePaymentRetry(true) + .prefillUserLoginDetails(prefill) + .build(); + + // Assert + assertNotNull(request); + assertEquals(merchantOrderId, request.getMerchantOrderId()); + assertEquals(amount, request.getAmount()); + assertEquals(3600L, request.getExpireAfter()); + assertTrue(request.getDisablePaymentRetry()); + assertNotNull(request.getPrefillUserLoginDetails()); + assertEquals("9876543210", request.getPrefillUserLoginDetails().getPhoneNumber()); + } +}