Skip to content

Initial version of sample code to call payments reseller APIs #10146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
20 changes: 20 additions & 0 deletions payments/reseller/subscription/snippets/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Pre-requisites:

You will need to be added to a specific allow list which will allow you to impersonate
the service account `payment-reseller-test-serv-734@eng-diagram-369411.iam.gserviceaccount.com`

<How to verify this as a developer?>
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This line appears to be a placeholder comment for the developer. It should be replaced with actual instructions or removed to finalize the documentation.


Steps:

- Save your google cloud credentials to a local file to run the code.
- Run the command `gcloud auth application-default login`. This should
trigger an oauth screen asking permissions run google cloud cli. Login with
the allow listed account and give permissions.

Credentials will be saved to a local file with directory path
`~/.config/gcloud/application_default_credentials.json`


- You are all setup, you can execute specific code
Verify with running an impersonate code which should print the access token
85 changes: 85 additions & 0 deletions payments/reseller/subscription/snippets/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The PR checklist indicates that the pom.xml parent should be set to the latest shared-configuration, but this is missing. Using the shared parent POM is important for maintaining consistency, managing dependencies, and inheriting standard plugin configurations across samples in the repository. Please add the parent configuration.


<groupId>com.example</groupId>
<artifactId>google-reseller-java</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>1.33.1</version>
</dependency>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-paymentsresellersubscription</artifactId>
<version>v1-rev20250302-2.0.0</version>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-gson</artifactId>
<version>1.43.3</version>
</dependency>
</dependencies>

<profiles>
<profile>
<id>runImpersonateServiceAccountAndProvisionSubscription</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.google.cloud.reseller.subscription.samples.ProvisionSubscription</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>runImpersonateServiceAccountAndGenerateUserSession</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
<configuration>
<mainClass>com.google.cloud.reseller.subscription.samples.GenerateUserSession</mainClass>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.google.cloud.reseller.subscription.samples;

public class Constants {
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This utility class with only static constants should not be instantiable. It's a good practice to make the class final and add a private constructor to prevent instantiation.

Suggested change
public class Constants {
public final class Constants {


public static final String PARTNER_ID = "yt_ptm_staging";
public static final String NFT_PRODUCT_ID = "YTPD-acb26288-d14b-4865-a748-a132c5f37f6d";
public static final String PRODUCT_ID = "YTPD-902fd916-219d-4aa0-b735-40fc57bd9b5b";
public static final String ELIGIBILITY_ID = "yt_ptm_staging:ibv2-bundle-plan";
public static final String PROMOTION = "YTPM-CilZVFBELTkwMmZkOTE2LTIxOWQtNGFhMC1iNzM1LTQwZmM1N2JkOWI1YhAC";
public static final String PROMOTION_SUB_LEVEL = "YTPM-CilZVFBELTkwMmZkOTE2LTIxOWQtNGFhMC1iNzM1LTQwZmM1N2JkOWI1YhAB";
public static final String PLAN_TYPE = "PARTNER_PLAN_TYPE_HARD_BUNDLE";
public static final String REGION_CODE = "US";
public static final String SUB_ID = "onboarding-partner-testing";
public static final String TARGET_SERVICE_ACCOUNT_EMAIL = "payment-reseller-test-serv-734@eng-diagram-369411.iam.gserviceaccount.com";


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.google.cloud.reseller.subscription.samples;

import static com.google.cloud.reseller.subscription.samples.Constants.ELIGIBILITY_ID;
import static com.google.cloud.reseller.subscription.samples.Constants.NFT_PRODUCT_ID;
import static com.google.cloud.reseller.subscription.samples.Constants.PARTNER_ID;
import static com.google.cloud.reseller.subscription.samples.Constants.PLAN_TYPE;
import static com.google.cloud.reseller.subscription.samples.Constants.PRODUCT_ID;
import static com.google.cloud.reseller.subscription.samples.Constants.PROMOTION;
import static com.google.cloud.reseller.subscription.samples.Constants.PROMOTION_SUB_LEVEL;
Comment on lines +7 to +9
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

These imports are unused and should be removed to keep the code clean.

import static com.google.cloud.reseller.subscription.samples.Constants.REGION_CODE;
import static com.google.cloud.reseller.subscription.samples.Constants.SUB_ID;
import static com.google.cloud.reseller.subscription.samples.Constants.TARGET_SERVICE_ACCOUNT_EMAIL;

import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.services.paymentsresellersubscription.v1.PaymentsResellerSubscription;
import com.google.api.services.paymentsresellersubscription.v1.model.GoogleCloudPaymentsResellerSubscriptionV1CreateSubscriptionIntent;
import com.google.api.services.paymentsresellersubscription.v1.model.GoogleCloudPaymentsResellerSubscriptionV1GenerateUserSessionRequest;
import com.google.api.services.paymentsresellersubscription.v1.model.GoogleCloudPaymentsResellerSubscriptionV1GenerateUserSessionResponse;
import com.google.api.services.paymentsresellersubscription.v1.model.GoogleCloudPaymentsResellerSubscriptionV1IntentPayload;
import com.google.api.services.paymentsresellersubscription.v1.model.GoogleCloudPaymentsResellerSubscriptionV1Location;
import com.google.api.services.paymentsresellersubscription.v1.model.GoogleCloudPaymentsResellerSubscriptionV1ProductPayload;
import com.google.api.services.paymentsresellersubscription.v1.model.GoogleCloudPaymentsResellerSubscriptionV1Subscription;
import com.google.api.services.paymentsresellersubscription.v1.model.GoogleCloudPaymentsResellerSubscriptionV1SubscriptionLineItem;
import com.google.api.services.paymentsresellersubscription.v1.model.GoogleCloudPaymentsResellerSubscriptionV1SubscriptionPromotionSpec;
import com.google.api.services.paymentsresellersubscription.v1.model.GoogleCloudPaymentsResellerSubscriptionV1YoutubePayload;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;

public class GenerateUserSession {

private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();

public static void main(String[] args) throws IOException {
List<String> scopes = Collections.singletonList(
"https://www.googleapis.com/auth/youtube.commerce.partnership.integrated-billing");

ImpersonateServiceAccount impersonateServiceAccount = new ImpersonateServiceAccount(scopes, TARGET_SERVICE_ACCOUNT_EMAIL);
impersonateServiceAccount.refreshCredentials();

// call provision API using service account credentials
HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(
impersonateServiceAccount.getImpersonatedCredentials());
PaymentsResellerSubscription client = new PaymentsResellerSubscription.Builder(
HTTP_TRANSPORT, GsonFactory.getDefaultInstance(),
requestInitializer
).setRootUrl("https://preprod-paymentsresellersubscription.googleapis.com").build();
System.out.println("partners/" + PARTNER_ID + "/products/" + NFT_PRODUCT_ID);
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(
ZoneOffset.UTC); // RFC 3339, with offset
String subscriptionId = SUB_ID + Instant.now().toEpochMilli();
GoogleCloudPaymentsResellerSubscriptionV1GenerateUserSessionResponse generateUserSessionResponse = client.partners()
.userSessions().generate("partners/" + PARTNER_ID,
new GoogleCloudPaymentsResellerSubscriptionV1GenerateUserSessionRequest().setIntentPayload(
new GoogleCloudPaymentsResellerSubscriptionV1IntentPayload().setCreateIntent(
new GoogleCloudPaymentsResellerSubscriptionV1CreateSubscriptionIntent().setParent(
"partners/" + PARTNER_ID).setSubscriptionId(subscriptionId)
.setSubscription(
new GoogleCloudPaymentsResellerSubscriptionV1Subscription()
.setLineItems(
ImmutableList
.of(new GoogleCloudPaymentsResellerSubscriptionV1SubscriptionLineItem()
.setProduct("partners/" + PARTNER_ID + "/products/"
+ NFT_PRODUCT_ID)
.setProductPayload(
new GoogleCloudPaymentsResellerSubscriptionV1ProductPayload()
.setYoutubePayload(
new GoogleCloudPaymentsResellerSubscriptionV1YoutubePayload()
.setPartnerEligibilityIds(
List.of(ELIGIBILITY_ID))
.setPartnerPlanType(PLAN_TYPE)
))
// .setLineItemPromotionSpecs(ImmutableList.of(new GoogleCloudPaymentsResellerSubscriptionV1SubscriptionPromotionSpec().setPromotion("partners/" + PARTNER_ID + "/promotions/" + PROMOTION)))
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This commented-out code should be removed to improve readability.

))
.setPartnerUserToken("[email protected]")
.setServiceLocation(
new GoogleCloudPaymentsResellerSubscriptionV1Location()
.setPostalCode("94043")
.setRegionCode(REGION_CODE))
.setPurchaseTime(formatter.format(Instant.now()))
// .setPromotionSpecs(ImmutableList.of(new GoogleCloudPaymentsResellerSubscriptionV1SubscriptionPromotionSpec().setPromotion("partners/" + PARTNER_ID + "/promotions/" + PROMOTION_SUB_LEVEL)))
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This commented-out code should be removed.

)))).execute();
System.out.println("Subscription Id::" + subscriptionId);
System.out.printf("Sandbox URL to create subscription:: https://serviceactivation.sandbox.google.com/subscription/new/%s \n", generateUserSessionResponse.getUserSession().getToken());

}
}
// mvn clean compile
// mvn exec:java -Dexec.mainClass="com.google.cloud.reseller.subscription.samples.GenerateUserSession"
Comment on lines +96 to +97
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Execution commands should be documented in the README.md file for better visibility and to keep the source code clean of such comments.


Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.google.cloud.reseller.subscription.samples;

import static com.google.cloud.reseller.subscription.samples.Constants.TARGET_SERVICE_ACCOUNT_EMAIL;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ImpersonatedCredentials;

import com.google.auth.oauth2.OAuth2Utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

/** A utility wrapper to impersonate a target service account for a list of provided oauth scopes. */
public class ImpersonateServiceAccount {
public static final String USER_HOME = System.getProperty("user.home");
private ImpersonatedCredentials impersonatedCredentials;

public ImpersonateServiceAccount(List<String> oauthScopes, String targetServiceAccount) {
try {
// File name output from: $gcloud auth application-default login
GoogleCredentials sourceCredentials = GoogleCredentials.fromStream(new FileInputStream(
USER_HOME.concat("/.config/gcloud/application_default_credentials.json")));
Comment on lines +22 to +23
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The path to the application default credentials file is hardcoded. This is not robust as the location can vary, and it's not platform-independent. It's better to use GoogleCredentials.getApplicationDefault() which automatically finds the credentials from the standard locations.

      GoogleCredentials sourceCredentials = GoogleCredentials.getApplicationDefault();


impersonatedCredentials = ImpersonatedCredentials.newBuilder()
.setSourceCredentials(sourceCredentials)
.setTargetPrincipal(targetServiceAccount)
.setScopes(oauthScopes)
.setDelegates(null) //Optional delegation
.setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
.build();

} catch (IOException e) {
System.err.println("Failed to get impersonated credentials: " + e.getMessage());
System.err.println(
"Ensure your source credentials have the 'Service Account Token Creator' role on the target SA and that ADC is set up correctly.");
}
}
Comment on lines +19 to +38
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

The constructor swallows the IOException and only prints to System.err. If an exception occurs (e.g., credentials file not found), impersonatedCredentials will be null, leading to a NullPointerException when its methods are called later. The constructor should propagate the IOException to the caller to ensure proper error handling. The callers in main methods already declare throws IOException, so this change will not require further modifications there.

  public ImpersonateServiceAccount(List<String> oauthScopes, String targetServiceAccount) throws IOException {
    // File name output from: $gcloud auth application-default login
    GoogleCredentials sourceCredentials = GoogleCredentials.fromStream(new FileInputStream(
        USER_HOME.concat("/.config/gcloud/application_default_credentials.json")));

    impersonatedCredentials = ImpersonatedCredentials.newBuilder()
        .setSourceCredentials(sourceCredentials)
        .setTargetPrincipal(targetServiceAccount)
        .setScopes(oauthScopes)
        .setDelegates(null) //Optional delegation
        .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
        .build();
  }


public void refreshCredentials() throws IOException {
impersonatedCredentials.refresh();
}

public String getAccessToken() {
return impersonatedCredentials.getAccessToken().getTokenValue();
}

public ImpersonatedCredentials getImpersonatedCredentials() {
return impersonatedCredentials;
}

public static void main(String[] args) throws IOException {
List<String> scopes = Collections.singletonList(
"https://www.googleapis.com/auth/youtube.commerce.partnership.integrated-billing");

ImpersonateServiceAccount impersonateServiceAccount = new ImpersonateServiceAccount(scopes, TARGET_SERVICE_ACCOUNT_EMAIL);
impersonateServiceAccount.refreshCredentials();
System.out.printf("AccessToken:: [%s]\n", impersonateServiceAccount.getAccessToken());
}
}

// mvn clean compile
// mvn exec:java -Dexec.mainClass="com.google.cloud.reseller.subscription.samples.ImpersonateServiceAccount"
Loading