@shopify/shopify-api
Version:
Shopify API Library for Node - accelerate development with support for authentication, graphql proxy, webhooks
185 lines (169 loc) • 4.83 kB
text/typescript
import {ConfigInterface} from '../base-types';
import {BillingError, GraphqlQueryError} from '../error';
import {GraphqlClient, graphqlClientClass} from '../clients/admin';
import {
AppSubscription,
BillingCreateUsageRecord,
BillingCreateUsageRecordParams,
UsageRecord,
UsageRecordCreateResponse,
Money,
} from './types';
import {assessPayments} from './check';
import {
convertAppRecurringPricingMoney,
convertAppUsagePricingMoney,
} from './utils';
interface InternalParams {
client: GraphqlClient;
isTest?: boolean;
}
interface CreateUsageRecordVariables {
description: string;
price: Money;
subscriptionLineItemId: string;
idempotencyKey?: string;
}
const CREATE_USAGE_RECORD_MUTATION = `
mutation appUsageRecordCreate($description: String!, $price: MoneyInput!, $subscriptionLineItemId: ID!) {
appUsageRecordCreate(description: $description, price: $price, subscriptionLineItemId: $subscriptionLineItemId) {
userErrors {
field
message
}
appUsageRecord {
id
description
idempotencyKey
price {
amount
currencyCode
}
subscriptionLineItem {
id
plan {
pricingDetails {
... on AppUsagePricing {
balanceUsed {
amount
currencyCode
}
cappedAmount {
amount
currencyCode
}
terms
}
}
}
}
}
}
}
`;
export function createUsageRecord(
config: ConfigInterface,
): BillingCreateUsageRecord {
return async function createUsageRecord(
usageRecordInfo: BillingCreateUsageRecordParams,
): Promise<UsageRecord> {
const {
session,
subscriptionLineItemId,
description,
price,
idempotencyKey,
isTest = true,
} = usageRecordInfo;
const GraphqlClient = graphqlClientClass({config});
const client = new GraphqlClient({session});
// If a subscription line item ID is not passed, we will query Shopify
// for an active usage subscription line item ID
const usageSubscriptionLineItemId = subscriptionLineItemId
? subscriptionLineItemId
: await getUsageRecordSubscriptionLineItemId({client, isTest});
const variables: CreateUsageRecordVariables = {
description,
price,
subscriptionLineItemId: usageSubscriptionLineItemId,
};
if (idempotencyKey) {
variables.idempotencyKey = idempotencyKey;
}
try {
const response = await client.request<UsageRecordCreateResponse>(
CREATE_USAGE_RECORD_MUTATION,
{
variables,
},
);
if (response.data?.appUsageRecordCreate?.userErrors.length) {
throw new BillingError({
message: 'Error while creating a usage record',
errorData: response.data?.appUsageRecordCreate?.userErrors,
});
}
const appUsageRecord =
response.data?.appUsageRecordCreate?.appUsageRecord!;
convertAppRecurringPricingMoney(appUsageRecord.price);
convertAppUsagePricingMoney(
appUsageRecord.subscriptionLineItem.plan.pricingDetails,
);
return appUsageRecord;
} catch (error) {
if (error instanceof GraphqlQueryError) {
throw new BillingError({
message: error.message,
errorData: error.response?.errors,
});
} else {
throw error;
}
}
};
}
async function getUsageRecordSubscriptionLineItemId({
client,
isTest,
}: InternalParams): Promise<string> {
const payments = await assessPayments({client, isTest});
if (!payments.hasActivePayment) {
throw new BillingError({
message: 'No active payment found',
errorData: [],
});
}
if (!payments.appSubscriptions.length) {
throw new BillingError({
message: 'No active subscriptions found',
errorData: [],
});
}
if (payments.appSubscriptions) {
const usageSubscriptionLineItemId = getUsageLineItemId(
payments.appSubscriptions,
);
return usageSubscriptionLineItemId;
}
throw new BillingError({
message: 'Unable to find active subscription line item',
errorData: [],
});
}
function getUsageLineItemId(subscriptions: AppSubscription[]): string {
for (const subscription of subscriptions) {
// An app can have only one active subscription
if (subscription.status === 'ACTIVE' && subscription.lineItems) {
// An app can have only one usage subscription line item
for (const lineItem of subscription.lineItems) {
if ('balanceUsed' in lineItem.plan.pricingDetails) {
return lineItem.id;
}
}
}
}
throw new BillingError({
message: 'No active usage subscription found',
errorData: [],
});
}