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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# CHANGELOG

## Next Release

- Removes the deprecated, unusable `ReferralCustomer.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

## v7.10.0 (2026-07-24)

- Adds missing `Eccn`, `Manufacturer`, and `PrintedCommodityIdentifier` properties to the `CustomsItem` API model
Expand Down
242 changes: 12 additions & 230 deletions EasyPost.Tests/ServicesTests/ReferralCustomerServiceTest.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using EasyPost.Exceptions;
using EasyPost.Exceptions.API;
using EasyPost.Exceptions.General;
using EasyPost.Http;
using EasyPost.Models.API;
using EasyPost.Tests._Utilities;
using EasyPost.Tests._Utilities.Attributes;
Expand Down Expand Up @@ -84,213 +80,6 @@ public async Task TestGetNextPage()
}
}

[Fact]
[CrudOperations.Update]
[Testing.Function]
public async Task TestAddCreditCardToUser()
{
UseMockClient(new List<TestUtils.MockRequest>
{
new(
new TestUtils.MockRequestMatchRules(Method.Get, @"v2\/partners\/stripe_public_key$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"public_key\":\"pk_test_12345\"}")
),
new(
new TestUtils.MockRequestMatchRules(Method.Post, @"https://api.stripe.com/v1/tokens$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"id\":\"tok_12345\"}")
),
new(
new TestUtils.MockRequestMatchRules(Method.Post, @"v2\/credit_cards$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, data: new PaymentMethod
{
Id = "summary_123",
Last4 = ((string)Fixtures.CreditCardDetails["number"]).Substring(12),
})
)
});

CreditCard card = new CreditCard(Fixtures.CreditCardDetails);

PaymentMethod paymentMethod = await Client.ReferralCustomer.AddCreditCardToUser(ReferralCustomerKey, card.Number, card.ExpirationMonth, card.ExpirationYear, card.Cvc, PaymentMethod.Priority.Primary);

Assert.NotNull(paymentMethod);
Assert.IsType<PaymentMethod>(paymentMethod);
Assert.NotNull(paymentMethod.Id);
Assert.EndsWith(paymentMethod.Last4, card.Number);

// Assert that the original API key was restored to the client properly after the request
Assert.Equal(TestUtils.GetApiKey(TestUtils.ApiKey.Mock), Client.ApiKeyInUse);
}

[Fact]
[Testing.Parameters]
public async Task TestAddCreditCardToUserDifferentPaymentMethodPriorities()
{
UseMockClient(new List<TestUtils.MockRequest>
{
new(
new TestUtils.MockRequestMatchRules(Method.Get, @"v2\/partners\/stripe_public_key$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"public_key\":\"pk_test_12345\"}")
),
new(
new TestUtils.MockRequestMatchRules(Method.Post, @"https://api.stripe.com/v1/tokens$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"id\":\"tok_12345\"}")
),
new(
new TestUtils.MockRequestMatchRules(Method.Post, @"v2\/credit_cards$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, data: new PaymentMethod
{
Id = "summary_123",
Last4 = ((string)Fixtures.CreditCardDetails["number"]).Substring(12),
})
)
});

CreditCard card = new CreditCard(Fixtures.CreditCardDetails);

PaymentMethod.Priority? priority = PaymentMethod.Priority.Primary;

PaymentMethod paymentMethod = await Client.ReferralCustomer.AddCreditCardToUser(ReferralCustomerKey, card.Number, card.ExpirationMonth, card.ExpirationYear, card.Cvc, priority);
Assert.NotNull(paymentMethod);
// If we've gotten here, no internal errors occurred on the method.

// Test with other priorities.
priority = PaymentMethod.Priority.Secondary;
paymentMethod = await Client.ReferralCustomer.AddCreditCardToUser(ReferralCustomerKey, card.Number, card.ExpirationMonth, card.ExpirationYear, card.Cvc, priority);
Assert.NotNull(paymentMethod);

priority = null; // Should internally default to primary priority if not specified.
paymentMethod = await Client.ReferralCustomer.AddCreditCardToUser(ReferralCustomerKey, card.Number, card.ExpirationMonth, card.ExpirationYear, card.Cvc, priority);
Assert.NotNull(paymentMethod);
}

[Fact]
[Testing.Exception]
public async Task TestAddCreditCardToUserNoPublicKey()
{
UseMockClient(new List<TestUtils.MockRequest>
{
{
new(
new TestUtils.MockRequestMatchRules(Method.Get, @"v2\/partners\/stripe_public_key$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"public_key\":\"\"}")
)
}
});

CreditCard card = new CreditCard(Fixtures.CreditCardDetails);

await Assert.ThrowsAsync<InternalServerError>(async () => await Client.ReferralCustomer.AddCreditCardToUser(ReferralCustomerKey, card.Number, card.ExpirationMonth, card.ExpirationYear, card.Cvc, PaymentMethod.Priority.Primary));
}

[Fact]
[Testing.Exception]
public async Task TestAddCreditCardToUserBadPublicKeyResponse()
{
UseMockClient(new List<TestUtils.MockRequest>
{
new(
new TestUtils.MockRequestMatchRules(Method.Get, @"v2\/partners\/stripe_public_key$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"not_public_key\":\"random\"}")
),
});

CreditCard card = new CreditCard(Fixtures.CreditCardDetails);

await Assert.ThrowsAsync<InternalServerError>(async () => await Client.ReferralCustomer.AddCreditCardToUser(ReferralCustomerKey, card.Number, card.ExpirationMonth, card.ExpirationYear, card.Cvc, PaymentMethod.Priority.Primary));
}

[Fact]
[Testing.Exception]
public async Task TestAddCreditCardToUserNoStripeToken()
{
UseMockClient(new List<TestUtils.MockRequest>
{
new(
new TestUtils.MockRequestMatchRules(Method.Get, @"v2\/partners\/stripe_public_key$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"public_key\":\"pk_test_12345\"}")
),
new(
new TestUtils.MockRequestMatchRules(Method.Post, @"https://api.stripe.com/v1/tokens$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"id\":\"\"}")
)
});

CreditCard card = new CreditCard(Fixtures.CreditCardDetails);

await Assert.ThrowsAsync<ExternalApiError>(async () => await Client.ReferralCustomer.AddCreditCardToUser(ReferralCustomerKey, card.Number, card.ExpirationMonth, card.ExpirationYear, card.Cvc, PaymentMethod.Priority.Primary));
}

[Fact]
[Testing.Exception]
public async Task TestAddCreditCardToUserStripeCantConnect()
{
UseMockClient(new List<TestUtils.MockRequest>
{
new(
new TestUtils.MockRequestMatchRules(Method.Get, @"v2\/partners\/stripe_public_key$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"public_key\":\"pk_test_12345\"}")
),
new(
new TestUtils.MockRequestMatchRules(Method.Post, @"https://api.stripe.com/v1/tokens$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.NotFound, content: "{}")
)
});

CreditCard card = new CreditCard(Fixtures.CreditCardDetails);

await Assert.ThrowsAsync<ExternalApiError>(async () => await Client.ReferralCustomer.AddCreditCardToUser(ReferralCustomerKey, card.Number, card.ExpirationMonth, card.ExpirationYear, card.Cvc, PaymentMethod.Priority.Primary));
}

[Fact]
[Testing.Exception]
public async Task TestAddCreditCardToUserStripeBadResponse()
{
UseMockClient(new List<TestUtils.MockRequest>
{
new(
new TestUtils.MockRequestMatchRules(Method.Get, @"v2\/partners\/stripe_public_key$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"public_key\":\"pk_test_12345\"}")
),
new(
new TestUtils.MockRequestMatchRules(Method.Post, @"https://api.stripe.com/v1/tokens$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"not_id\":\"random\"}")
)
});

CreditCard card = new CreditCard(Fixtures.CreditCardDetails);

await Assert.ThrowsAsync<ExternalApiError>(async () => await Client.ReferralCustomer.AddCreditCardToUser(ReferralCustomerKey, card.Number, card.ExpirationMonth, card.ExpirationYear, card.Cvc, PaymentMethod.Priority.Primary));
}

[Fact]
[Testing.Exception]
public async Task TestAddCreditCardToUserEasyPostApiError()
{
UseMockClient(new List<TestUtils.MockRequest>
{
new(
new TestUtils.MockRequestMatchRules(Method.Get, @"v2\/partners\/stripe_public_key$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"public_key\":\"pk_test_12345\"}")
),
new(
new TestUtils.MockRequestMatchRules(Method.Post, @"https://api.stripe.com/v1/tokens$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.OK, content: "{\"id\":\"tok_12345\"}")
),
new(
new TestUtils.MockRequestMatchRules(Method.Post, @"v2\/credit_cards$"),
new TestUtils.MockRequestResponseInfo(HttpStatusCode.NotFound, content: "{}")
)
});

CreditCard card = new CreditCard(Fixtures.CreditCardDetails);

await Assert.ThrowsAsync<NotFoundError>(async () => await Client.ReferralCustomer.AddCreditCardToUser(ReferralCustomerKey, card.Number, card.ExpirationMonth, card.ExpirationYear, card.Cvc, PaymentMethod.Priority.Primary));

// Assert that the original API key was restored to the client properly even after the failed request
Assert.Equal(TestUtils.GetApiKey(TestUtils.ApiKey.Mock), Client.ApiKeyInUse);
}

[Fact]
[CrudOperations.Update]
[Testing.Function]
Expand Down Expand Up @@ -350,29 +139,22 @@ await Client.ReferralCustomer.AddBankAccountFromStripe(
);
}

#endregion

#endregion

private static string ReferralCustomerKey => TestUtils.GetApiKey(TestUtils.ApiKey.Referral);

private sealed class CreditCard
[Fact]
[Testing.Function]
public async Task TestRetrieveEasypostStripeApiKey()
{
internal readonly string Number;
UseVCR("retrieve_easypost_stripe_api_key");

internal readonly int ExpirationMonth;
string? publicKey = await Client.ReferralCustomer.RetrieveEasypostStripeApiKey();

internal readonly int ExpirationYear;
Assert.IsType<string>(publicKey);
Assert.StartsWith("pk_", publicKey);
}

internal readonly string Cvc;
#endregion

internal CreditCard(IReadOnlyDictionary<string, object> details)
{
Number = (string)details["number"];
ExpirationMonth = int.Parse((string)details["expiration_month"], NumberStyles.Number, CultureInfo.InvariantCulture);
ExpirationYear = int.Parse((string)details["expiration_year"], NumberStyles.Number, CultureInfo.InvariantCulture);
Cvc = (string)details["cvc"];
}
}
#endregion

private static string ReferralCustomerKey => TestUtils.GetApiKey(TestUtils.ApiKey.Referral);
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading