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 `addCreditCard` 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

## v9.0.0-rc.1 (2026-09-08)

- Breaking: Node 18+ is now required (built-in `fetch`)
Expand Down
146 changes: 12 additions & 134 deletions src/services/referral_customer_service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import util from 'util';

import Constants from '../constants';
import EasyPostClient from '../easypost';
import ExternalApiError from '../errors/api/external_api_error';
import User from '../models/user';
import baseService from './base_service';
import type { PaymentMethodObject } from './billing_service';
Expand All @@ -23,7 +19,6 @@ type ReferralCreateParameters = Record<string, unknown> & {
type MandateData = Record<string, unknown>;
type ReferralCustomerListResponse = { referral_customers: User[]; has_more: boolean };
type ReferralScopedClient = Pick<EasyPostClient, '_post'>;
type EasyPostHttpClient = Pick<EasyPostClient, '_get' | '_put'>;

/**
* Get an instance of the EasyPostClient using the referral user's API key.
Expand All @@ -38,96 +33,6 @@ function _getReferralClient(client: EasyPostClient, referralApiKey: string): Eas
});
}

/**
* Get EasyPost's Stripe API key used to create credit cards on Stripe's servers.
* @private
* @param {EasyPostClient} easypostClient - The EasyPostClient to use.
* @returns {string} - The Stripe API key.
*/
async function _getEasyPostStripeKey(easypostClient: EasyPostHttpClient): Promise<string> {
const url = 'partners/stripe_public_key';

const response = await easypostClient._get(url);

const body = response.body as { public_key: string };
return body.public_key;
}

/**
* Send the credit card details to Stripe to get a Stripe credit card token.
* @private
* @param {string} stripeKey - The Stripe API key.
* @param {string} number - Credit card number.
* @param {string} expirationMonth - Credit card expiration month.
* @param {string} expirationYear - Credit card expiration year.
* @param {string} cvc - Credit card CVC.
* @returns {Promise<string>} - Stripe credit card token.
*/
async function _sendCardDetailsToStripe(
stripeKey: string,
number: string,
expirationMonth: string,
expirationYear: string,
cvc: string,
): Promise<string> {
const searchParams = new URLSearchParams({
'card[number]': number,
'card[exp_month]': expirationMonth,
'card[exp_year]': expirationYear,
'card[cvc]': cvc,
});
const url = `https://api.stripe.com/v1/tokens?${searchParams.toString()}`;

try {
const response = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${stripeKey}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
});

if (!response.ok) {
throw new Error('Failed Stripe request');
}

const body = await response.json();

return body.id as string;
} catch (error) {
throw new ExternalApiError({
message: util.format(Constants.EXTERNAL_API_CALL_FAILED, 'Stripe'),
code: undefined,
statusCode: undefined,
errors: undefined,
});
}
}

/**
* Send the Stripe credit card token to EasyPost to add the card to the user's account.
* @private
* @param {EasyPostClient} client - The EasyPostClient to use.
* @param {string} referralApiKey - The referral user's production API key.
* @param {string} stripeCreditCardToken - Stripe credit card token.
* @param {string} priority - Whether to add the card as the 'primary' or 'secondary' card.
* @returns {Object} - Response body (EasyPost payment method object).
*/
async function _sendCardDetailsToEasyPost(
client: EasyPostClient,
referralApiKey: string,
stripeCreditCardToken: string,
priority: string,
): Promise<PaymentMethodObject> {
const _client = _getReferralClient(client, referralApiKey);
const url = 'credit_cards';
const params = { credit_card: { stripe_object_id: stripeCreditCardToken, priority } };

const response = await (_client as ReferralScopedClient)._post(url, params);

return response.body;
}

export default (easypostClient: EasyPostClient) =>
/**
* The ReferralCustomerService class provides methods for interacting with EasyPost {@link User referral customer} objects.
Expand Down Expand Up @@ -166,45 +71,6 @@ export default (easypostClient: EasyPostClient) =>
return true;
}

/**
* Add a credit card to EasyPost for a ReferralCustomer without needing a Stripe account. This function requires the ReferralCustomer User's API key.
* See {@link https://docs.easypost.com/docs/users/billing#create-credit-card EasyPost API Documentation} for more information.
* @param {string} referralApiKey - The referral customer's production API key.
* @param {string} number - The credit card number.
* @param {string} expirationMonth - The credit card expiration month.
* @param {string} expirationYear - The credit card expiration year.
* @param {string} cvc - The credit card CVC.
* @param {string} priority - Whether to add the card as 'primary' or 'secondary' payment method (defaults to 'primary').
* @returns {Object} - An object representing the newly-added credit card.
*/
static async addCreditCard(
referralApiKey: string,
number: string,
expirationMonth: string,
expirationYear: string,
cvc: string,
priority: string = 'primary',
): Promise<PaymentMethodObject> {
const stripeKey = await _getEasyPostStripeKey(easypostClient); // will throw if there's an error

const stripeCreditCardId = await _sendCardDetailsToStripe(
stripeKey,
number,
expirationMonth,
expirationYear,
cvc,
); // will throw if there's an error

const paymentMethod = await _sendCardDetailsToEasyPost(
easypostClient,
referralApiKey,
stripeCreditCardId,
priority,
); // will throw if there's an error

return paymentMethod;
}

/**
* 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.
Expand All @@ -229,6 +95,18 @@ export default (easypostClient: EasyPostClient) =>
return this._convertToEasyPostObject(response.body, params);
}

/**
* Retrieve EasyPost's Stripe public API key.
* @returns {string} - The Stripe API key.
*/
static async retrieveEasyPostStripeApiKey(): Promise<string> {
const url = 'partners/stripe_public_key';
const response = await easypostClient._get(url);
const body = response.body as { public_key: string };

return body.public_key;
}

/**
* Add a bank account to EasyPost for a ReferralCustomer.
* This function requires the ReferralCustomer User's API key.
Expand Down
Loading
Loading