Skip to content

Commit

Permalink
feat: list transactions and show payment method
Browse files Browse the repository at this point in the history
  • Loading branch information
darkoatanasovski committed Dec 14, 2022
1 parent a7e8157 commit b053923
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 91 deletions.
44 changes: 44 additions & 0 deletions src/services/cleeng.subscription.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { patch, get } from './cleeng.service';

import { addQueryParams } from '#src/utils/formatting';
import type { GetPaymentDetails, GetSubscriptions, GetTransactions, UpdateSubscription } from '#types/subscription';

export async function getActiveSubscription({ sandbox, customerId, jwt }: { sandbox: boolean; customerId: string; jwt: string }) {
const response = await getSubscriptions({ customerId }, sandbox, jwt);

if (response.errors.length > 0) return null;

return response.responseData.items.find((item) => item.status === 'active' || item.status === 'cancelled') || null;
}

export async function getAllTransactions({ sandbox, customerId, jwt }: { sandbox: boolean; customerId: string; jwt: string }) {
const response = await getTransactions({ customerId }, sandbox, jwt);

if (response.errors.length > 0) return null;

return response.responseData.items;
}

export async function getActivePayment({ sandbox, customerId, jwt }: { sandbox: boolean; customerId: string; jwt: string }) {
const response = await getPaymentDetails({ customerId }, sandbox, jwt);

if (response.errors.length > 0) return null;

return response.responseData.paymentDetails.find((paymentDetails) => paymentDetails.active) || null;
}

export const getSubscriptions: GetSubscriptions = async (payload, sandbox, jwt) => {
return get(sandbox, `/customers/${payload.customerId}/subscriptions`, jwt);
};

export const updateSubscription: UpdateSubscription = async (payload, sandbox, jwt) => {
return patch(sandbox, `/customers/${payload.customerId}/subscriptions`, JSON.stringify(payload), jwt);
};

export const getPaymentDetails: GetPaymentDetails = async (payload, sandbox, jwt) => {
return get(sandbox, `/customers/${payload.customerId}/payment_details`, jwt);
};

export const getTransactions: GetTransactions = async ({ customerId, limit, offset }, sandbox, jwt) => {
return get(sandbox, addQueryParams(`/customers/${customerId}/transactions`, { limit, offset }), jwt);
};
6 changes: 3 additions & 3 deletions src/services/inplayer.account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type {
UpdateCustomerConsents,
} from '#types/account';
import type { Config } from '#types/Config';
import type { InPlayerAuthData, InPlayerError, InPlayerResponse } from '#types/inplayer';
import type { InPlayerAuthData, InPlayerError } from '#types/inplayer';

enum InPlayerEnv {
Development = 'development',
Expand Down Expand Up @@ -112,7 +112,7 @@ export const getFreshJwtToken = async ({ auth }: { auth: AuthData }) => auth;

export const updateCustomer: UpdateCustomer = async (customer) => {
try {
const response: InPlayerResponse<AccountData> = await InPlayer.Account.updateAccount(processUpdateAccount(customer));
const response = await InPlayer.Account.updateAccount(processUpdateAccount(customer));

return {
errors: [],
Expand Down Expand Up @@ -164,7 +164,7 @@ export const updateCustomerConsents: UpdateCustomerConsents = async (payload) =>
const { customer, consents } = payload;
const params = { ...processUpdateAccount(customer), ...{ metadata: { consents: JSON.stringify(consents) } } };

const { data }: InPlayerResponse<AccountData> = await InPlayer.Account.updateAccount(params);
const { data } = await InPlayer.Account.updateAccount(params);

return {
consents: parseJson(data?.metadata?.consents as string, []),
Expand Down
144 changes: 144 additions & 0 deletions src/services/inplayer.subscription.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import InPlayer, { Card, SubscriptionDetails as InplayerSubscription } from '@inplayer-org/inplayer.js';

import type { PaymentDetail, Subscription, Transaction } from '#types/subscription';
import type { Config } from '#types/Config';
import type { InPlayerPurchaseDetails } from '#types/inplayer';

interface SubscriptionDetails extends InplayerSubscription {
item_id?: number;
item_title?: string;
subscription_id?: string;
subscription_price?: number;
action_type?: 'recurrent' | 'canceled' | 'free-trial' | 'ended' | 'incomplete_expired';
next_rebill_date?: number;
charged_amount?: number;
payment_method_name?: string;
access_type?: {
period: string;
};
}

export async function getActiveSubscription({ config }: { config: Config }) {
try {
const assetId = config.integrations.inplayer?.assetId || 0;
const { data: hasAccess } = await InPlayer.Asset.checkAccessForAsset(assetId);

if (hasAccess) {
const { data } = await InPlayer.Subscription.getSubscriptions();
const activeSubscription = data.collection.find((subscription: SubscriptionDetails) => subscription.item_id === assetId);
if (activeSubscription) {
return processActiveSubscription(activeSubscription);
}
}
return null;
} catch {
throw new Error('Unable to fetch customer subscriptions.');
}
}

export async function getAllTransactions() {
try {
const { data } = await InPlayer.Payment.getPurchaseHistory('active', 0, 30);
// @ts-ignore
// TODO fix PurchaseHistoryCollection type in InPlayer SDK
return data?.collection?.map((transaction: InPlayerPurchaseDetails) => processTransaction(transaction));
} catch {
throw new Error('Failed to get transactions');
}
}

export async function getActivePayment() {
try {
const { data } = await InPlayer.Payment.getDefaultCreditCard();
const cards: PaymentDetail[] = [];
for (const currency in data?.cards) {
// @ts-ignore
// TODO fix Card type in InPlayer SDK
cards.push(processCardDetails(data.cards?.[currency]));
}

return cards.find((paymentDetails) => paymentDetails.active) || null;
} catch {
throw new Error('Failed to get payment details');
}
}

export const getSubscriptions = async () => {
return {
errors: [],
responseData: { items: [] },
};
};

const processCardDetails = (card: Card & { card_type: string; account_id: number }): PaymentDetail => {
const { number, exp_month, exp_year, card_name, card_type, account_id } = card;
const zeroFillExpMonth = `0${exp_month}`.substring(-2);
return {
customerId: account_id.toString(),
paymentMethodSpecificParams: {
holderName: card_name,
variant: card_type,
lastCardFourDigits: number,
cardExpirationDate: `${zeroFillExpMonth}/${exp_year}`,
},
active: true,
} as PaymentDetail;
};

// TODO: fix PurchaseDetails type in InPlayer SDK
const processTransaction = (transaction: InPlayerPurchaseDetails): Transaction => {
return {
transactionId: transaction.parent_resource_id,
transactionDate: transaction.created_at,
offerId: transaction.purchased_access_fee_id?.toString(),
offerType: transaction.type || '',
offerTitle: transaction?.purchased_access_fee_description || '',
offerPeriod: '',
transactionPriceExclTax: transaction.purchased_amount?.toString(),
transactionCurrency: transaction.purchased_currency,
discountedOfferPrice: transaction.purchased_amount?.toString(),
offerCurrency: transaction.purchased_currency,
offerPriceExclTax: transaction.purchased_amount?.toString(),
applicableTax: '0',
transactionPriceInclTax: transaction.purchased_amount?.toString(),
customerId: transaction.customer_id?.toString(),
customerEmail: transaction.consumer_email,
customerLocale: '',
customerCountry: 'en',
customerIpCountry: '',
customerCurrency: '',
paymentMethod: transaction.payment_method,
};
};

const processActiveSubscription = (subscription: SubscriptionDetails) => {
let status = '';
switch (subscription.action_type) {
case 'free-trial' || 'recurrent':
status = 'active';
break;
case 'canceled':
status = 'cancelled';
break;
case 'incomplete_expired' || 'ended':
status = 'expired';
break;
default:
status = 'terminated';
}

return {
subscriptionId: subscription.subscription_id,
offerId: subscription.item_id?.toString(),
status,
expiresAt: subscription.next_rebill_date,
nextPaymentAt: subscription.next_rebill_date,
nextPaymentPrice: subscription.subscription_price,
nextPaymentCurrency: subscription.currency,
paymentGateway: 'stripe',
paymentMethod: subscription.payment_method_name,
offerTitle: subscription.item_title,
period: subscription.access_type?.period,
totalPrice: subscription.charged_amount,
} as Subscription;
};
20 changes: 0 additions & 20 deletions src/services/subscription.service.ts

This file was deleted.

Loading

0 comments on commit b053923

Please sign in to comment.