Skip to content

Commit

Permalink
Merge pull request #1863 from Shopify/sle-c/convert-money-type
Browse files Browse the repository at this point in the history
[Fix] Convert received Money.amount from string to number
  • Loading branch information
Arkham authored Feb 3, 2025
2 parents 6c0397b + 4827313 commit a298784
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 7 deletions.
5 changes: 5 additions & 0 deletions .changeset/dry-onions-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/shopify-api': patch
---

Convert all Money.amount returned from the Billing GraphQL API from string to number type
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ export const APP_USAGE_RECORD = {
pricingDetails: {
balanceUsed: USAGE_RECORD_PRICE,
cappedAmount: {
amount: '5.00',
amount: 5,
currencyCode: 'USD',
},
terms: '1 dollar per usage',
Expand Down
4 changes: 4 additions & 0 deletions packages/apps/shopify-api/lib/billing/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
CurrentAppInstallations,
OneTimePurchase,
} from './types';
import {convertLineItems} from './utils';

interface SubscriptionMeetsCriteriaParams {
subscription: AppSubscription;
Expand Down Expand Up @@ -87,6 +88,9 @@ export async function assessPayments({
installation.activeSubscriptions.forEach((subscription) => {
if (subscriptionMeetsCriteria({subscription, isTest, plans})) {
returnValue.hasActivePayment = true;
if (subscription.lineItems) {
subscription.lineItems = convertLineItems(subscription.lineItems);
}
returnValue.appSubscriptions.push(subscription);
}
});
Expand Down
9 changes: 7 additions & 2 deletions packages/apps/shopify-api/lib/billing/create-usage-record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Money,
} from './types';
import {assessPayments} from './check';

import {convertAppRecurringPricingMoney, convertAppUsagePricingMoney} from './utils';
interface InternalParams {
client: GraphqlClient;
isTest?: boolean;
Expand Down Expand Up @@ -108,7 +108,12 @@ export function createUsageRecord(
errorData: response.data?.appUsageRecordCreate?.userErrors,
});
}
return response.data?.appUsageRecordCreate?.appUsageRecord!;

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({
Expand Down
17 changes: 16 additions & 1 deletion packages/apps/shopify-api/lib/billing/subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
BillingSubscriptions,
SubscriptionResponse,
} from './types';
import {convertLineItems} from './utils';

const SUBSCRIPTION_QUERY = `
query appSubscription {
Expand Down Expand Up @@ -81,6 +82,20 @@ export function subscriptions(config: ConfigInterface): BillingSubscriptions {
const response =
await client.request<SubscriptionResponse>(SUBSCRIPTION_QUERY);

return response.data?.currentAppInstallation!;
if (!response.data?.currentAppInstallation?.activeSubscriptions) {
return {activeSubscriptions: []};
}

const activeSubscriptions =
response.data.currentAppInstallation.activeSubscriptions;
activeSubscriptions.forEach((subscription) => {
if (subscription.lineItems) {
subscription.lineItems = convertLineItems(subscription.lineItems);
}
});

return {
activeSubscriptions,
};
};
}
15 changes: 15 additions & 0 deletions packages/apps/shopify-api/lib/billing/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,21 @@ export interface UsageRecord {
* The idempotency key for this request.
*/
idempotencyKey?: string;
/**
* The subscription line item associated with the usage record.
*/
subscriptionLineItem: AppSubscriptionLineItem;
}

export interface AppSubscriptionLineItem {
/**
* The ID of the subscription line item.
*/
id: string;
/**
* The plan associated with the subscription line item.
*/
plan: AppPlan;
}

export interface BillingUpdateUsageCappedAmountParams {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
BillingUpdateUsageCappedAmountResponse,
UpdateCappedAmountConfirmation,
} from './types';
import {convertLineItems} from './utils';

const UPDATE_USAGE_CAPPED_AMOUNT_MUTATION = `
mutation appSubscriptionLineItemUpdate($cappedAmount: MoneyInput!, $id: ID!) {
Expand Down Expand Up @@ -88,11 +89,16 @@ export function updateUsageCappedAmount(
});
}

const appSubscription =
response.data?.appSubscriptionLineItemUpdate?.appSubscription!;
if (appSubscription && appSubscription.lineItems) {
appSubscription.lineItems = convertLineItems(appSubscription.lineItems);
}

return {
confirmationUrl:
response.data?.appSubscriptionLineItemUpdate?.confirmationUrl!,
appSubscription:
response.data?.appSubscriptionLineItemUpdate?.appSubscription!,
appSubscription,
};
} catch (error) {
if (error instanceof GraphqlQueryError) {
Expand Down
71 changes: 71 additions & 0 deletions packages/apps/shopify-api/lib/billing/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {ActiveSubscriptionLineItem} from './types';

/**
* Converts string amounts to numbers in Money type objects
*/
export function convertMoneyAmount(data: any) {
if (!data) return data;

convertAppUsagePricingMoney(data);
convertAppRecurringPricingMoney(data);
convertAppDiscountMoney(data);

return data;
}

export function convertAppRecurringPricingMoney(data: any): void {
if (!data) return;

if (data.price?.amount && typeof data.price.amount === 'string') {
data.price.amount = parseFloat(data.price.amount);
}
}

export function convertAppDiscountMoney(data: any): void {
if (!data) return;

if (
data.discount?.priceAfterDiscount?.amount &&
typeof data.discount.priceAfterDiscount.amount === 'string'
) {
data.discount.priceAfterDiscount.amount = parseFloat(
data.discount.priceAfterDiscount.amount,
);
}

if (
data.discount?.value?.amount?.amount &&
typeof data.discount.value.amount.amount === 'string'
) {
data.discount.value.amount.amount = parseFloat(
data.discount.value.amount.amount,
);
}
}

export function convertAppUsagePricingMoney(data: any): void {
if (!data) return;

if (data.balanceUsed?.amount && typeof data.balanceUsed.amount === 'string') {
data.balanceUsed.amount = parseFloat(data.balanceUsed.amount);
}

if (
data.cappedAmount?.amount &&
typeof data.cappedAmount.amount === 'string'
) {
data.cappedAmount.amount = parseFloat(data.cappedAmount.amount);
}
}

/**
* Converts Money amounts in line items
*/
export function convertLineItems(lineItems: ActiveSubscriptionLineItem[]) {
return lineItems.map((item) => {
if (item.plan?.pricingDetails) {
item.plan.pricingDetails = convertMoneyAmount(item.plan.pricingDetails);
}
return item;
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export const USAGE_RECORD = {
plan: {
pricingDetails: {
balanceUsed: {amount: 1, currencyCode: 'USD'},
cappedAmount: {amount: '5.00', currencyCode: 'USD'},
cappedAmount: {amount: 5, currencyCode: 'USD'},
terms: '1 dollar per usage',
},
},
Expand Down

0 comments on commit a298784

Please sign in to comment.