From 1b93071dad3779b41014ed5bfef78d945385e39b Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Thu, 24 Sep 2026 09:57:26 -0600 Subject: [PATCH 1/2] chore: remove deprecated, unused addCreditCardToUser function Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 6 + .../service/ReferralCustomerService.java | 149 +----------------- .../retrieve_easypost_stripe_api_key.json | 94 +++++++++++ .../com/easypost/ReferralCustomerTest.java | 52 ++---- 4 files changed, 116 insertions(+), 185 deletions(-) create mode 100644 src/test/cassettes/referral_customer/retrieve_easypost_stripe_api_key.json diff --git a/CHANGELOG.md b/CHANGELOG.md index cc6bc464f..d39070a8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## Next Release + +- Removes the deprecated, unusable `addCreditCardToUser` function + - Stripe has disabled the ability to pass plain credit card details over the wire and now requires using [Stripe.js/Elements/Checkout](https://support.stripe.com/questions/card-tokenization-restrictions-using-publishable-keys). Follow the [Decentralized (EasyPost-Manage Billing) Guide](https://docs.easypost.com/guides/get-started-with-forge/easypost-managed-billing-guide#referralcustomer-billing-management) for more details on the new flow to use. + - Makes `referralCustomer.retrieveEasypostStripeApiKey` public to help facilitate adding credit cards using Stripe.js + ## v8.8.0 (2026-06-25) - Adds `params` to `requestPin` ensuring users can pass `easypost_details` to the call diff --git a/src/main/java/com/easypost/service/ReferralCustomerService.java b/src/main/java/com/easypost/service/ReferralCustomerService.java index e21f2320f..0a0671066 100644 --- a/src/main/java/com/easypost/service/ReferralCustomerService.java +++ b/src/main/java/com/easypost/service/ReferralCustomerService.java @@ -1,9 +1,5 @@ package com.easypost.service; -import com.easypost.Constants; -import com.easypost.EasyPost; -import com.easypost.exception.API.EncodingError; -import com.easypost.exception.API.ExternalApiError; import com.easypost.exception.EasyPostException; import com.easypost.exception.General.EndOfPaginationError; import com.easypost.http.Requestor; @@ -12,15 +8,8 @@ import com.easypost.model.PaymentMethod; import com.easypost.model.PaymentMethodObject; import com.easypost.model.ReferralCustomer; -import com.easypost.utils.InternalUtilities; import lombok.SneakyThrows; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @@ -118,54 +107,6 @@ public ReferralCustomerCollection apply(Map parameters) { }, collection.getReferralCustomers(), pageSize); } - /** - * Add credit card to a referral user. This function requires the Referral - * User's API key. - * - * @param referralApiKey API key of the referral user. - * @param number Credit card number. - * @param expirationMonth Expiration month of the credit card. - * @param expirationYear Expiration year of the credit card. - * @param cvc CVC of the credit card. - * @return PaymentMethodObject object. - * @throws EasyPostException when the request fails. - */ - public PaymentMethodObject addCreditCardToUser(final String referralApiKey, final String number, - final int expirationMonth, final int expirationYear, - final String cvc) throws EasyPostException { - return addCreditCardToUser(referralApiKey, number, expirationMonth, expirationYear, cvc, - PaymentMethod.Priority.PRIMARY); - } - - /** - * Add credit card to a referral user. This function requires the Referral - * User's API key. - * - * @param referralApiKey API key of the referral user. - * @param number Credit card number. - * @param expirationMonth Expiration month of the credit card. - * @param expirationYear Expiration year of the credit card. - * @param cvc CVC of the credit card. - * @param priority Priority of this credit card. - * @return PaymentMethodObject object. - * @throws EasyPostException when the request fails. - */ - public PaymentMethodObject addCreditCardToUser(final String referralApiKey, final String number, - final int expirationMonth, final int expirationYear, - final String cvc, final PaymentMethod.Priority priority) - throws EasyPostException { - String easypostStripeApiKey = retrieveEasypostStripeApiKey(); - String stripeToken; - - try { - stripeToken = createStripeToken(number, expirationMonth, expirationYear, cvc, easypostStripeApiKey); - } catch (Exception e) { - throw new ExternalApiError(String.format(Constants.ErrorMessages.EXTERNAL_API_CALL_FAILED, "Stripe")); - } - - return createEasypostCreditCard(referralApiKey, stripeToken, priority.toString().toLowerCase()); - } - /** * Add a credit card to EasyPost for a ReferralCustomer with a payment method ID from Stripe. * This function requires the ReferralCustomer User's API key. @@ -225,7 +166,7 @@ public PaymentMethodObject addBankAccountFromStripe(final String referralApiKey, * @return EasyPost Stripe API key. * @throws EasyPostException when the request fails. */ - private String retrieveEasypostStripeApiKey() throws EasyPostException { + public String retrieveEasypostStripeApiKey() throws EasyPostException { String endpoint = "partners/stripe_public_key"; @SuppressWarnings ("unchecked") Map response = @@ -233,92 +174,4 @@ private String retrieveEasypostStripeApiKey() throws EasyPostException { return response.getOrDefault("public_key", ""); } - - /** - * Get credit card token from Stripe. - * - * @param number Credit card number. - * @param expirationMonth Expiration month of the credit card. - * @param expirationYear Expiration year of the credit card. - * @param cvc CVC of the credit card. - * @param easypostStripeApiKey EasyPost Stripe API key. - * @return Stripe token. - * @throws EncodingError when the request details could not be encoded. - * @throws IOException when the request fails. - */ - private static String createStripeToken(final String number, final int expirationMonth, final int expirationYear, - final String cvc, final String easypostStripeApiKey) - throws EncodingError, IOException { - String apiToken = String.format("%s %s", "Bearer", easypostStripeApiKey); - - Map params = new HashMap<>(); - params.put("number", number); - params.put("exp_month", String.valueOf(expirationMonth)); - params.put("exp_year", String.valueOf(expirationYear)); - params.put("cvc", cvc); - - String encodedURL = InternalUtilities.getEncodedURL(params, "card"); - URL stripeUrl = new URL("https://api.stripe.com/v1/tokens?" + encodedURL); - - HttpURLConnection conn; - if (EasyPost._vcrUrlFunction != null) { - conn = EasyPost._vcrUrlFunction.apply(stripeUrl.toString()); - } else { - conn = (HttpURLConnection) stripeUrl.openConnection(); - } - - conn.setRequestMethod("POST"); - conn.setRequestProperty("Authorization", apiToken); - conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - conn.setDoOutput(true); - - StringBuilder response; - - try (BufferedReader br = new BufferedReader( - new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { - String line; - response = new StringBuilder(); - - while ((line = br.readLine()) != null) { - response.append(line); - response.append(System.lineSeparator()); - } - br.close(); - } finally { - conn.disconnect(); - } - - String responseBody = response.toString(); - - @SuppressWarnings ("unchecked") Map responseMap = - Constants.Http.GSON.fromJson(responseBody, Map.class); - - return responseMap.get("id").toString(); - } - - /** - * Submit Stripe credit card token to EasyPost. - * - * @param referralApiKey API key of the referral user. - * @param stripeObjectId Stripe token. - * @param priority Credit card priority. - * @return CreditCard object. - * @throws EasyPostException when the request fails. - */ - private PaymentMethodObject createEasypostCreditCard(final String referralApiKey, final String stripeObjectId, - final String priority) throws EasyPostException { - Map params = new HashMap<>(); - params.put("stripe_object_id", stripeObjectId); - params.put("priority", priority); - - Map wrappedParams = new HashMap<>(); - wrappedParams.put("credit_card", params); - - EasyPostClient referralClient = new EasyPostClient(referralApiKey); - - String endpoint = "credit_cards"; - - return Requestor.request(RequestMethod.POST, endpoint, wrappedParams, PaymentMethodObject.class, - referralClient); - } } diff --git a/src/test/cassettes/referral_customer/retrieve_easypost_stripe_api_key.json b/src/test/cassettes/referral_customer/retrieve_easypost_stripe_api_key.json new file mode 100644 index 000000000..798258284 --- /dev/null +++ b/src/test/cassettes/referral_customer/retrieve_easypost_stripe_api_key.json @@ -0,0 +1,94 @@ +[ + { + "recordedAt": 1790265422, + "request": { + "body": "", + "method": "GET", + "headers": { + "Accept-Charset": [ + "UTF-8" + ], + "User-Agent": [ + "REDACTED" + ] + }, + "uri": "https://api.easypost.com/v2/partners/stripe_public_key" + }, + "response": { + "body": "{\n \"public_key\": \"pk_x3JSr5eOVWNTLRej8cZDde9VQ0AT5\"\n}", + "httpVersion": null, + "headers": { + "null": [ + "HTTP/1.1 200 OK" + ], + "content-length": [ + "49" + ], + "expires": [ + "0" + ], + "x-node": [ + "web114azw" + ], + "vary": [ + "EasyPost-Api-Version" + ], + "x-frame-options": [ + "SAMEORIGIN" + ], + "x-download-options": [ + "noopen" + ], + "x-permitted-cross-domain-policies": [ + "none" + ], + "x-backend": [ + "easypost" + ], + "pragma": [ + "no-cache" + ], + "strict-transport-security": [ + "max-age\u003d31536000; includeSubDomains; preload" + ], + "x-xss-protection": [ + "1; mode\u003dblock" + ], + "x-content-type-options": [ + "nosniff" + ], + "easypost-api-version": [ + "2015-01-01" + ], + "x-ep-request-uuid": [ + "c10b2c396ab5484ee2b8ff3002260dfc" + ], + "x-proxied": [ + "intlb1azw f29267e751", + "extlb1azw 07fb4d8f06" + ], + "referrer-policy": [ + "strict-origin-when-cross-origin" + ], + "x-runtime": [ + "0.023460" + ], + "content-type": [ + "application/json; charset\u003dutf-8" + ], + "x-version-label": [ + "easypost-202609241501-949351e54f-main" + ], + "cache-control": [ + "private, no-cache, no-store" + ] + }, + "status": { + "code": 200, + "message": "OK" + }, + "uri": "https://api.easypost.com/v2/partners/stripe_public_key" + }, + "duration": 126 + } +] \ No newline at end of file diff --git a/src/test/java/com/easypost/ReferralCustomerTest.java b/src/test/java/com/easypost/ReferralCustomerTest.java index fecd51a5e..60d7d93ac 100644 --- a/src/test/java/com/easypost/ReferralCustomerTest.java +++ b/src/test/java/com/easypost/ReferralCustomerTest.java @@ -1,12 +1,10 @@ package com.easypost; -import com.easypost.exception.API.ExternalApiError; import com.easypost.exception.EasyPostException; import com.easypost.exception.API.InvalidRequestError; import com.easypost.exception.General.EndOfPaginationError; import com.easypost.exception.API.NotFoundError; import com.easypost.model.PaymentMethod; -import com.easypost.model.PaymentMethodObject; import com.easypost.model.ReferralCustomer; import com.easypost.model.ReferralCustomerCollection; import org.junit.jupiter.api.BeforeAll; @@ -137,41 +135,6 @@ public void testGetNextPage() throws EasyPostException { } } - /** - * Test adding a credit card to a Referral user. - * - * @throws EasyPostException when the request fails. - */ - @Test - public void testReferralAddCreditCard() throws Exception { - vcr.setUpTest("referral_add_credit_card"); - - Map creditCardDetails = Fixtures.creditCardDetails(); - PaymentMethodObject creditCard = vcr.client.referralCustomer.addCreditCardToUser(referralUserKey(), - (String) creditCardDetails.get("number"), - Integer.parseInt((String) creditCardDetails.get("expiration_month")), - Integer.parseInt((String) creditCardDetails.get("expiration_year")), - (String) creditCardDetails.get("cvc"), PaymentMethod.Priority.PRIMARY); - - assertInstanceOf(PaymentMethodObject.class, creditCard); - assertTrue(creditCard.getId().startsWith("pm_")); - assertEquals(((String) Fixtures.creditCardDetails().get("number")).substring(12), creditCard.getLast4()); - } - - /** - * Test creating bad Stripe token with invalid input parameters. - * - * @throws Exception when the request fails. - */ - @Test - public void testCreateBadStripeToken() throws Exception { - vcr.setUpTest("create_bad_stripe_token"); - - assertThrows(ExternalApiError.class, - () -> vcr.client.referralCustomer.addCreditCardToUser(referralUserKey(), "1234", 1234, 1234, "1234", - PaymentMethod.Priority.PRIMARY)); - } - /** * Test adding a credit card from Stripe for a Referral user raises an error when it fails. * @@ -215,4 +178,19 @@ public void testAddBankAccountFromStripe() throws EasyPostException { exception.getMessage() ); } + + /** + * Test retrieving EasyPost Stripe API key. + * + * @throws EasyPostException when the request fails. + */ + @Test + public void testRetrieveEasypostStripeApiKey() throws EasyPostException { + vcr.setUpTest("retrieve_easypost_stripe_api_key"); + + String publicKey = vcr.client.referralCustomer.retrieveEasypostStripeApiKey(); + + assertInstanceOf(String.class, publicKey); + assertTrue(publicKey.startsWith("pk_")); + } } From 0ad1d587a864a81d6b1e96410d31dd0035b1e3d9 Mon Sep 17 00:00:00 2001 From: Justintime50 <39606064+Justintime50@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:02:37 -0600 Subject: [PATCH 2/2] chore: remove add_credit_card cassette Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../referral_add_credit_card.json | 280 ------------------ 1 file changed, 280 deletions(-) delete mode 100644 src/test/cassettes/referral_customer/referral_add_credit_card.json diff --git a/src/test/cassettes/referral_customer/referral_add_credit_card.json b/src/test/cassettes/referral_customer/referral_add_credit_card.json deleted file mode 100644 index 8045f040c..000000000 --- a/src/test/cassettes/referral_customer/referral_add_credit_card.json +++ /dev/null @@ -1,280 +0,0 @@ -[ - { - "recordedAt": 1723824484, - "request": { - "body": "", - "method": "GET", - "headers": { - "Accept-Charset": [ - "UTF-8" - ], - "User-Agent": [ - "REDACTED" - ] - }, - "uri": "https://api.easypost.com/v2/partners/stripe_public_key" - }, - "response": { - "body": "{\n \"public_key\": \"pk_x3JSr5eOVWNTLRej8cZDde9VQ0AT5\"\n}", - "httpVersion": null, - "headers": { - "null": [ - "HTTP/1.1 200 OK" - ], - "content-length": [ - "49" - ], - "expires": [ - "0" - ], - "x-node": [ - "bigweb36nuq" - ], - "x-frame-options": [ - "SAMEORIGIN" - ], - "x-download-options": [ - "noopen" - ], - "x-permitted-cross-domain-policies": [ - "none" - ], - "x-backend": [ - "easypost" - ], - "pragma": [ - "no-cache" - ], - "strict-transport-security": [ - "max-age\u003d31536000; includeSubDomains; preload" - ], - "x-xss-protection": [ - "1; mode\u003dblock" - ], - "x-content-type-options": [ - "nosniff" - ], - "x-ep-request-uuid": [ - "0580883166bf7964e787564d003794f6" - ], - "x-proxied": [ - "intlb3nuq c0f5e722d1", - "extlb2nuq b6e1b5034c" - ], - "referrer-policy": [ - "strict-origin-when-cross-origin" - ], - "x-runtime": [ - "0.026426" - ], - "content-type": [ - "application/json; charset\u003dutf-8" - ], - "x-version-label": [ - "easypost-202408152333-48cda4a73e-master" - ], - "cache-control": [ - "private, no-cache, no-store" - ] - }, - "status": { - "code": 200, - "message": "OK" - }, - "uri": "https://api.easypost.com/v2/partners/stripe_public_key" - }, - "duration": 224 - }, - { - "recordedAt": 1723824484, - "request": { - "body": "", - "method": "POST", - "headers": { - "Content-Type": [ - "application/x-www-form-urlencoded" - ] - }, - "uri": "https://api.stripe.com/v1/tokens?card%5Bnumber%5D\u003d4536410136126170\u0026card%5Bcvc%5D\u003d778\u0026card%5Bexp_month%5D\u003d5\u0026card%5Bexp_year%5D\u003d2028?%63%61%72%64%5B%63%76%63%5D\u003d%52%45%44%41%43%54%45%44\u0026%63%61%72%64%5B%65%78%70%5F%79%65%61%72%5D\u003d%32%30%32%38\u0026%63%61%72%64%5B%6E%75%6D%62%65%72%5D\u003d%52%45%44%41%43%54%45%44\u0026%63%61%72%64%5B%65%78%70%5F%6D%6F%6E%74%68%5D\u003d%35" - }, - "response": { - "body": "{\n \"livemode\": true,\n \"created\": 1.723824484E9,\n \"client_ip\": \"REDACTED\",\n \"id\": \"tok_0PoSh6DqT4huGUvd5K2dAq31\",\n \"used\": false,\n \"type\": \"card\",\n \"card\": {\n \"address_zip_check\": null,\n \"country\": \"US\",\n \"last4\": \"6170\",\n \"funding\": \"credit\",\n \"wallet\": null,\n \"address_country\": null,\n \"address_state\": null,\n \"exp_month\": 5.0,\n \"exp_year\": 2028.0,\n \"networks\": {\n \"preferred\": null\n },\n \"address_city\": null,\n \"tokenization_method\": null,\n \"cvc_check\": \"unchecked\",\n \"address_line2\": null,\n \"address_line1\": null,\n \"name\": null,\n \"id\": \"card_0PoSh6DqT4huGUvduHN4IpK3\",\n \"address_line1_check\": null,\n \"address_zip\": null,\n \"dynamic_last4\": null,\n \"brand\": \"Visa\",\n \"object\": \"card\"\n },\n \"object\": \"token\"\n}", - "httpVersion": null, - "headers": { - "null": [ - "HTTP/1.1 200 OK" - ], - "Server": [ - "nginx" - ], - "Access-Control-Allow-Origin": [ - "*" - ], - "Access-Control-Allow-Methods": [ - "GET,HEAD,PUT,PATCH,POST,DELETE" - ], - "Idempotency-Key": [ - "940f9c59-9223-4e4b-a63e-29bf2a73e9d7" - ], - "Original-Request": [ - "req_kWaAqXWA7o5QHJ" - ], - "Stripe-Version": [ - "2020-08-27" - ], - "Stripe-Should-Retry": [ - "false" - ], - "Access-Control-Expose-Headers": [ - "Request-Id, Stripe-Manage-Version, Stripe-Should-Retry, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required" - ], - "Strict-Transport-Security": [ - "max-age\u003d63072000; includeSubDomains; preload" - ], - "Access-Control-Allow-Credentials": [ - "true" - ], - "Report-To": [ - "{\"group\":\"coop\",\"max_age\":8640,\"endpoints\":[{\"url\":\"https://q.stripe.com/coop-report?s\u003dpayins-bapi-srv\"}],\"include_subdomains\":true}" - ], - "Content-Security-Policy": [ - "report-uri https://q.stripe.com/csp-report?p\u003dv1%2Ftokens; block-all-mixed-content; default-src \u0027none\u0027; base-uri \u0027none\u0027; form-action \u0027none\u0027; frame-ancestors \u0027none\u0027; img-src \u0027self\u0027; script-src \u0027self\u0027 \u0027report-sample\u0027; style-src \u0027self\u0027" - ], - "Cross-Origin-Opener-Policy-Report-Only": [ - "same-origin; report-to\u003d\"coop\"" - ], - "Content-Length": [ - "788" - ], - "Access-Control-Max-Age": [ - "300" - ], - "Request-Id": [ - "req_kWaAqXWA7o5QHJ" - ], - "Content-Type": [ - "application/json" - ], - "X-Content-Type-Options": [ - "nosniff" - ], - "Connection": [ - "keep-alive" - ], - "Reporting-Endpoints": [ - "coop\u003d\"https://q.stripe.com/coop-report?s\u003dpayins-bapi-srv\"" - ], - "Date": [ - "Fri, 16 Aug 2024 16:08:04 GMT" - ], - "X-Stripe-Priority-Routing-Enabled": [ - "true" - ], - "Cache-Control": [ - "no-cache, no-store" - ], - "Vary": [ - "Origin" - ], - "X-Stripe-Routing-Context-Priority-Tier": [ - "livemode-critical" - ] - }, - "status": { - "code": 200, - "message": "OK" - }, - "uri": "https://api.stripe.com/v1/tokens?card%5Bnumber%5D\u003d4536410136126170\u0026card%5Bcvc%5D\u003d778\u0026card%5Bexp_month%5D\u003d5\u0026card%5Bexp_year%5D\u003d2028?%63%61%72%64%5B%63%76%63%5D\u003d%52%45%44%41%43%54%45%44\u0026%63%61%72%64%5B%65%78%70%5F%79%65%61%72%5D\u003d%32%30%32%38\u0026%63%61%72%64%5B%6E%75%6D%62%65%72%5D\u003d%52%45%44%41%43%54%45%44\u0026%63%61%72%64%5B%65%78%70%5F%6D%6F%6E%74%68%5D\u003d%35" - }, - "duration": 506 - }, - { - "recordedAt": 1723824487, - "request": { - "body": "{\n \"credit_card\": {\n \"stripe_object_id\": \"tok_0PoSh6DqT4huGUvd5K2dAq31\",\n \"priority\": \"primary\"\n }\n}", - "method": "POST", - "headers": { - "Accept-Charset": [ - "UTF-8" - ], - "User-Agent": [ - "REDACTED" - ], - "Content-Type": [ - "application/json" - ] - }, - "uri": "https://api.easypost.com/v2/credit_cards" - }, - "response": { - "body": "{\n \"last4\": \"6170\",\n \"disabled_at\": null,\n \"name\": null,\n \"exp_month\": 5.0,\n \"id\": \"pm_1ff40e4f4ee54a2eab85b957f199c089\",\n \"exp_year\": 2028.0,\n \"requires_mandate_collection\": false,\n \"brand\": \"Visa\",\n \"object\": \"CreditCard\"\n}", - "httpVersion": null, - "headers": { - "null": [ - "HTTP/1.1 201 Created" - ], - "content-length": [ - "193" - ], - "expires": [ - "0" - ], - "x-node": [ - "bigweb39nuq" - ], - "x-frame-options": [ - "SAMEORIGIN" - ], - "x-download-options": [ - "noopen" - ], - "x-permitted-cross-domain-policies": [ - "none" - ], - "x-backend": [ - "easypost" - ], - "pragma": [ - "no-cache" - ], - "strict-transport-security": [ - "max-age\u003d31536000; includeSubDomains; preload" - ], - "x-xss-protection": [ - "1; mode\u003dblock" - ], - "x-content-type-options": [ - "nosniff" - ], - "x-ep-request-uuid": [ - "0580883666bf7965e7875667003795de" - ], - "x-proxied": [ - "intlb4nuq c0f5e722d1", - "extlb2nuq b6e1b5034c" - ], - "referrer-policy": [ - "strict-origin-when-cross-origin" - ], - "x-runtime": [ - "2.623311" - ], - "content-type": [ - "application/json; charset\u003dutf-8" - ], - "x-version-label": [ - "easypost-202408152333-48cda4a73e-master" - ], - "cache-control": [ - "private, no-cache, no-store" - ] - }, - "status": { - "code": 201, - "message": "Created" - }, - "uri": "https://api.easypost.com/v2/credit_cards" - }, - "duration": 2826 - } -] \ No newline at end of file