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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ dependency-reduced-pom.xml
.DS_Store
**/.DS_Store
.run
checkstyle.xml
checkstyle.xml
.jvm.config
.mvn*
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.phonepe</groupId>
<artifactId>pg-sdk-java</artifactId>
<version>2.2.2</version>
<version>2.2.3</version>
<name>B2B JAVA SDK</name>
<url>https://github.com/PhonePe/phonepe-pg-sdk-java</url>
<description>Phonepe PG JAVA SDK</description>
Expand Down
12 changes: 8 additions & 4 deletions src/main/java/com/phonepe/sdk/pg/Env.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,22 @@ public enum Env {
SANDBOX(
BaseUrl.SANDBOX_PG_HOST_URL,
BaseUrl.SANDBOX_OAUTH_HOST_URL,
BaseUrl.SANDBOX_EVENTS_HOST_URL), PRODUCTION(
BaseUrl.SANDBOX_EVENTS_HOST_URL,
BaseUrl.SANDBOX_PG_HOST_URL), PRODUCTION(
BaseUrl.PRODUCTION_PG_HOST_URL,
BaseUrl.PRODUCTION_OAUTH_HOST_URL,
BaseUrl.PRODUCTION_EVENTS_HOST_URL), TEST(BaseUrl.TESTING_URL, BaseUrl.TESTING_URL,
BaseUrl.TESTING_URL);
BaseUrl.PRODUCTION_EVENTS_HOST_URL,
BaseUrl.PRODUCTION_PG_HOST_URL_PCI), TEST(BaseUrl.TESTING_URL, BaseUrl.TESTING_URL,
BaseUrl.TESTING_URL, BaseUrl.TESTING_URL);

private final String pgHostUrl;
private final String oAuthHostUrl;
private final String eventsHostUrl;
private final String pciPgHostUrl;

Env(String pgHostUrl, String oAuthHostUrl, String eventsHostUrl) {
Env(String pgHostUrl, String oAuthHostUrl, String eventsHostUrl, String pciPgHostUrl) {
this.pgHostUrl = pgHostUrl;
this.pciPgHostUrl = pciPgHostUrl;
this.oAuthHostUrl = oAuthHostUrl;
this.eventsHostUrl = eventsHostUrl;
}
Expand Down
16 changes: 15 additions & 1 deletion src/main/java/com/phonepe/sdk/pg/common/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ protected <T, R> T requestViaAuthRefresh(
Map<String, String> queryParams,
TypeReference<T> responseTypeReference,
List<HttpHeaderPair> headers) {
return requestViaAuthRefresh(
methodName, requestData, url, queryParams, responseTypeReference, headers,
this.env.getPgHostUrl());
}

@SneakyThrows
protected <T, R> T requestViaAuthRefresh(
HttpMethodType methodName,
R requestData,
String url,
Map<String, String> queryParams,
TypeReference<T> responseTypeReference,
List<HttpHeaderPair> headers,
String hostUrl) {
List<HttpHeaderPair> httpHeaders = new ArrayList<>(headers);
HttpCommand<T, R> httpCommand = HttpCommand.<T, R>builder()
.client(this.okHttpClient)
Expand All @@ -91,7 +105,7 @@ protected <T, R> T requestViaAuthRefresh(
.methodName(methodName)
.headers(addAuthHeader(httpHeaders))
.requestData(requestData)
.hostURL(this.env.getPgHostUrl())
.hostURL(hostUrl)
.encodingType(APPLICATION_JSON)
.queryParams(queryParams)
.url(url)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
public class BaseUrl {

public static final String PRODUCTION_PG_HOST_URL = "https://api.phonepe.com/apis/pg";
public static final String PRODUCTION_PG_HOST_URL_PCI = "https://cards.phonepe.com/apis/pg";
public static final String SANDBOX_PG_HOST_URL = "https://api-preprod.phonepe.com/apis/pg-sandbox";

public static final String PRODUCTION_OAUTH_HOST_URL = "https://api.phonepe.com/apis/identity-manager";
Expand Down
93 changes: 91 additions & 2 deletions src/main/java/com/phonepe/sdk/pg/common/models/MetaInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,111 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import java.util.regex.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(Include.NON_NULL)
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(Include.NON_NULL)
public class MetaInfo {

/** Free-text field. Max 256 characters. */
private String udf1;

/** Free-text field. Max 256 characters. */
private String udf2;

/** Free-text field. Max 256 characters. */
private String udf3;

/** Free-text field. Max 256 characters. */
private String udf4;

/** Free-text field. Max 256 characters. */
private String udf5;

/** Free-text field. Max 256 characters. */
private String udf6;

/** Free-text field. Max 256 characters. */
private String udf7;

/** Free-text field. Max 256 characters. */
private String udf8;

/** Free-text field. Max 256 characters. */
private String udf9;

/** Free-text field. Max 256 characters. */
private String udf10;

/** Alphanumeric + [_ - @ . +] only. Max 50 characters. */
private String udf11;

/** Alphanumeric + [_ - @ . +] only. Max 50 characters. */
private String udf12;

/** Alphanumeric + [_ - @ . +] only. Max 50 characters. */
private String udf13;

/** Alphanumeric + [_ - @ . +] only. Max 50 characters. */
private String udf14;

/** Alphanumeric + [_ - @ . +] only. Max 50 characters. */
private String udf15;

// Lombok fills in all field setters; only build() is customised here.
public static class MetaInfoBuilder {

private static final Pattern RESTRICTED_PATTERN = Pattern.compile("^[a-zA-Z0-9_\\- @.+]*$");

public MetaInfo build() {
validateSize("udf1", udf1, 256);
validateSize("udf2", udf2, 256);
validateSize("udf3", udf3, 256);
validateSize("udf4", udf4, 256);
validateSize("udf5", udf5, 256);
validateSize("udf6", udf6, 256);
validateSize("udf7", udf7, 256);
validateSize("udf8", udf8, 256);
validateSize("udf9", udf9, 256);
validateSize("udf10", udf10, 256);
validateSizeAndPattern("udf11", udf11, 50);
validateSizeAndPattern("udf12", udf12, 50);
validateSizeAndPattern("udf13", udf13, 50);
validateSizeAndPattern("udf14", udf14, 50);
validateSizeAndPattern("udf15", udf15, 50);
return new MetaInfo(udf1, udf2, udf3, udf4, udf5,
udf6, udf7, udf8, udf9, udf10,
udf11, udf12, udf13, udf14, udf15);
}

private static void validateSize(String field, String value, int max) {
if (value != null && value.length() > max) {
throw new IllegalArgumentException(
field + " exceeds maximum allowed size of " + max + " characters");
}
}

private static void validateSizeAndPattern(String field, String value, int max) {
if (value == null) {
return;
}
if (value.length() > max) {
throw new IllegalArgumentException(
field + " exceeds maximum allowed size of " + max + " characters");
}
if (!RESTRICTED_PATTERN.matcher(value)
.matches()) {
throw new IllegalArgumentException(field
+ " should only contain alphanumeric characters, underscores, hyphens, spaces, @, ., and +");
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,19 @@
import com.phonepe.sdk.pg.common.http.HttpMethodType;
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.request.instruments.PaymentV2Instrument;
import com.phonepe.sdk.pg.common.models.response.CallbackResponse;
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 com.phonepe.sdk.pg.payments.v2.customcheckout.filters.DeviceOsHeaderFilter;
import com.phonepe.sdk.pg.payments.v2.customcheckout.filters.InstrumentRequestFilter;
import com.phonepe.sdk.pg.payments.v2.customcheckout.filters.PayContext;
import com.phonepe.sdk.pg.payments.v2.customcheckout.filters.PciHostFilter;
import com.phonepe.sdk.pg.payments.v2.models.request.CreateSdkOrderRequest;
import com.phonepe.sdk.pg.payments.v2.models.request.PgPaymentFlow;
import com.phonepe.sdk.pg.payments.v2.models.response.CreateSdkOrderResponse;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -47,6 +53,7 @@
public class CustomCheckoutClient extends BaseClient {

private List<HttpHeaderPair> headers;
private final List<InstrumentRequestFilter> instrumentFilters;

private CustomCheckoutClient(
final String clientId,
Expand All @@ -59,6 +66,9 @@ private CustomCheckoutClient(
BaseEvent.buildInitClientEvent(
FlowType.PG, EventType.CUSTOM_CHECKOUT_CLIENT_INITIALIZED));
this.prepareHeaders();
// PciHostFilter must be listed before header-injecting filters (e.g. DeviceOsHeaderFilter)
// so that any future filter which reads ctx.getHostUrl() sees the PCI host already set.
this.instrumentFilters = List.of(new PciHostFilter(env), new DeviceOsHeaderFilter());
Comment thread
282000ypk marked this conversation as resolved.
}

/**
Expand Down Expand Up @@ -110,23 +120,22 @@ public static CustomCheckoutClient getInstance(
public PgPaymentResponse pay(PgPaymentRequest pgPaymentRequest) {
String url = CustomCheckoutConstants.PAY_API;
try {
List<HttpHeaderPair> requestHeaders = headers;
if (pgPaymentRequest.getDeviceOS() != null && !pgPaymentRequest.getDeviceOS()
.isBlank()) {
requestHeaders = new ArrayList<>(headers);
requestHeaders.add(
HttpHeaderPair.builder()
.key(Headers.X_DEVICE_OS)
.value(pgPaymentRequest.getDeviceOS())
.build());
}
PaymentV2Instrument instrument = resolveInstrument(pgPaymentRequest);
PayContext ctx = new PayContext(
this.getEnv()
.getPgHostUrl(), new ArrayList<>(headers),
pgPaymentRequest.getDeviceOS());
instrumentFilters.stream()
.filter(f -> f.supports(instrument))
.forEach(f -> f.apply(ctx));
PgPaymentResponse pgPaymentResponse = requestViaAuthRefresh(
HttpMethodType.POST,
pgPaymentRequest,
url,
null,
new TypeReference<PgPaymentResponse>() {},
requestHeaders);
ctx.getHeaders(),
ctx.getHostUrl());
this.eventPublisher.send(
BaseEvent.buildCustomCheckoutPayEvent(
EventState.SUCCESS, pgPaymentRequest, url, EventType.PAY_SUCCESS));
Expand All @@ -143,6 +152,13 @@ public PgPaymentResponse pay(PgPaymentRequest pgPaymentRequest) {
}
}

private PaymentV2Instrument resolveInstrument(PgPaymentRequest pgPaymentRequest) {
if (!(pgPaymentRequest.getPaymentFlow() instanceof PgPaymentFlow)) {
return null;
}
return ((PgPaymentFlow) pgPaymentRequest.getPaymentFlow()).getPaymentMode();
}

/**
* Gets the status of the order
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.customcheckout.filters;

import com.phonepe.sdk.pg.common.constants.Headers;
import com.phonepe.sdk.pg.common.http.HttpHeaderPair;
import com.phonepe.sdk.pg.common.models.request.instruments.PaymentV2Instrument;

/**
* Injects the x-device-os header when deviceOS is present on the request.
* Applies to all instrument types.
*/
public class DeviceOsHeaderFilter implements InstrumentRequestFilter {

@Override
public boolean supports(PaymentV2Instrument instrument) {
return true;
}

@Override
public void apply(PayContext ctx) {
if (ctx.getDeviceOS() != null && !ctx.getDeviceOS()
.isBlank()) {
ctx.getHeaders()
.add(
HttpHeaderPair.builder()
.key(Headers.X_DEVICE_OS)
.value(ctx.getDeviceOS())
.build());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.customcheckout.filters;

import com.phonepe.sdk.pg.common.models.request.instruments.PaymentV2Instrument;

/**
* Strategy interface for modifying pay request context (host URL, headers)
* based on the payment instrument. Implement this to add new routing or
* header injection rules without modifying CustomCheckoutClient.
*/
public interface InstrumentRequestFilter {

/**
* Returns true if this filter applies to the given instrument.
*/
boolean supports(PaymentV2Instrument instrument);

/**
* Applies modifications to the PayContext (host URL and/or headers).
*/
void apply(PayContext ctx);
}
Loading
Loading