import { last } from 'lodash' ; import Stripe from 'stripe' ; import { COUNTRY_CODE , DAY , LOCAL_STRIPE , METADATA_KEY , OAUTH_CALLBACK_ERROR_CODE , PAYMENT_CURRENCY , PAYMENT_MAX_DECIMAL_PLACES , } from 'common/enums' ; import { environment , isProd , isTest } from 'common/environment' ; import { PaymentAmountInvalidError , ServerError } from 'common/errors' ; import logger from 'common/logger' ; import { getUTCNextMonday , getUTCNextMonthDayOne , toProviderAmount , } from 'common/utils' ; import SlackService from 'connectors/slack' ; import { User } from 'definitions' ; see { url see { url see { url see { url class StripeService { stripeAPI : Stripe ; constructor ( ) { let options = { } ; if ( isTest ) { options = LOCAL_STRIPE ; } this . stripeAPI = new Stripe ( environment . stripeSecret , { apiVersion : '2020-08-27' , ... options , } ) ; } handleError ( err ) { const slack = new SlackService ( ) ; logger . error ( err ) ; switch ( err . code ) { case 'parameter_invalid_integer' : throw new PaymentAmountInvalidError ( `maximum ${ PAYMENT_MAX_DECIMAL_PLACES } decimal places` ) ; default : slack . sendStripeAlert ( { data : { type : err . type , code : err . code } , message : err . message , } ) ; throw new ServerError ( `failed to process the stripe request: ${ err . message }` ) ; } } /**
     * Customer
     */ createCustomer = async ( { user } ) => { try { return await this . stripeAPI . customers . create ( { email : user . email , metadata : { [ METADATA_KEY . USER_ID ] : user . id } , } ) ; } catch ( err ) { this . handleError ( err ) ; } } ; updateCustomer = async ( { id , paymentMethod , } ) => { try { return await this . stripeAPI . customers . update ( id , { invoice_settings : { default_payment_method : paymentMethod , } , } ) ; } catch ( err ) { this . handleError ( err ) ; } } ; getPaymentMethod = async ( id ) => { try { return await this . stripeAPI . paymentMethods . retrieve ( id ) ; } catch ( err ) { this . handleError ( err ) ; } } ; param customerId param amount param currency createPaymentIntent = async ( { customerId , amount , currency , } ) => { try { return await this . stripeAPI . paymentIntents . create ( { customer : customerId , amount : toProviderAmount ( { amount } ) , currency , } ) ; } catch ( err ) { this . handleError ( err ) ; } } ; param customerId param amount param currency createSetupIntent = async ( { customerId , metadata , } ) => { try { return await this . stripeAPI . setupIntents . create ( { customer : customerId , payment_method_types : [ 'card' ] , usage : 'off_session' , metadata , } ) ; } catch ( err ) { this . handleError ( err ) ; } } ; see { url createExpressAccount = async ( { country , user , } ) => { const isUS = country === 'UnitedStates' ; const returnUrlPrefix = `${ environment . siteDomain }/oauth/stripe-connect` ; try { const account = await this . stripeAPI . accounts . create ( { type : 'express' , country : COUNTRY_CODE [ country ] , email : user . email , metadata : { [ METADATA_KEY . USER_ID ] : user . id } , capabilities : { transfers : { requested : true } } , ... ( isUS ? { } : { tos_acceptance : { service_agreement : 'recipient' } } ) , } ) ; const { url } = await this . stripeAPI . accountLinks . create ( { account : account . id , type : 'account_onboarding' , refresh_url : `${ returnUrlPrefix }/failure?code=${ OAUTH_CALLBACK_ERROR_CODE . stripeAccountRefresh }` , return_url : `${ returnUrlPrefix }/success` , } ) ; return { accountId : account . id , country : account . country , currency : account . default_currency , onboardingUrl : url , } ; } catch ( err ) { this . handleError ( err ) ; } } ; createExpressLoginLink = async ( accountId ) => { const { url } = await this . stripeAPI . accounts . createLoginLink ( accountId ) ; return url ; } ; see { url transfer = async ( { amount , currency , recipientStripeConnectedId , txId , } ) => { try { return await this . stripeAPI . transfers . create ( { amount : toProviderAmount ( { amount } ) , currency , destination : recipientStripeConnectedId , metadata : { [ METADATA_KEY . TX_ID ] : txId } , } ) ; } catch ( error ) { this . handleError ( error ) ; } } ; /**
     * Product & Price
     */ createProduct = async ( { name , owner } ) => { try { return await this . stripeAPI . products . create ( { name , metadata : { [ METADATA_KEY . USER_ID ] : owner } , } ) ; } catch ( error ) { this . handleError ( error ) ; } } ; updateProduct = async ( { id , name } ) => { try { return await this . stripeAPI . products . update ( id , { name } ) ; } catch ( error ) { this . handleError ( error ) ; } } ; createPrice = async ( { amount , currency , interval , productId , } ) => { try { return await this . stripeAPI . prices . create ( { currency , product : productId , recurring : { interval } , unit_amount : toProviderAmount ( { amount } ) , } ) ; } catch ( error ) { this . handleError ( error ) ; } } ; /**
     * Subscription
     */ createSubscription = async ( { customer , price , } ) => { try { const trialEndAt = ( isProd ? getUTCNextMonthDayOne ( ) : getUTCNextMonday ( ) ) / 1000 ; return await this . stripeAPI . subscriptions . create ( { trial_end : trialEndAt , customer , items : [ { price } ] , proration_behavior : 'none' , } ) ; } catch ( error ) { this . handleError ( error ) ; } } ; cancelSubscription = async ( id ) => { try { return await this . stripeAPI . subscriptions . del ( id , { prorate : false } ) ; } catch ( error ) { this . handleError ( error ) ; } } ; createSubscriptionItem = async ( { price , subscription , } ) => { try { return await this . stripeAPI . subscriptionItems . create ( { price , proration_behavior : 'none' , quantity : 1 , subscription , } ) ; } catch ( error ) { this . handleError ( error ) ; } } ; deleteSubscriptionItem = async ( id ) => { try { return await this . stripeAPI . subscriptionItems . del ( id , { proration_behavior : 'none' , } ) ; } catch ( error ) { this . handleError ( error ) ; } } ; listSubscriptionItems = async ( id ) => { try { return await this . stripeAPI . subscriptionItems . list ( { subscription : id , limit : 100 , } ) ; } catch ( error ) { this . handleError ( error ) ; } } ; /**
     * Get customer portal URL
     */ getCustomerPortal = async ( { customerId } ) => { try { const session = await this . stripeAPI . billingPortal . sessions . create ( { customer : customerId , } ) ; return session . url ; } catch ( error ) { this . handleError ( error ) ; } } ; /**
     * Get delivery failed events in last 7 days.
     */ getDeliveryFailedEvents = async ( ) => { try { let cursor ; let fetch = true ; let hasMore = true ; const now = Date . now ( ) ; const threeDays = DAY * 3 ; const events = [ ] ; while ( hasMore && fetch ) { const batch = await this . stripeAPI . events . list ( { delivery_success : false , limit : 50 , starting_after : cursor , } ) ; const data = batch ?. data || [ ] ; data . map ( ( event ) => { const time = ( event ?. created || 0 ) * 1000 ; if ( now - time < threeDays ) { events . push ( event ) ; } else { fetch = false ; } } ) ; hasMore = ! ! batch ?. has_more ; cursor = last ( events ) ?. id ; } return events ; } catch ( error ) { this . handleError ( error ) ; } } ; } export const stripe = new StripeService ( ) ;