import { Injectable , OnDestroy } from '@angular/core' ; import { BehaviorSubject , combineLatest , Observable , Subscription } from 'rxjs' ; import { map , tap , distinctUntilChanged } from 'rxjs/operators' ; import { MindsUser } from '../../../interfaces/entities' ; import { ApiService } from '../../../common/api/api.service' ; import { Wallet , WalletV2Service , } from '../../wallet/components/wallet-v2.service' ; import { WireService as WireV1Service , WireStruc } from '../wire.service' ; import { UpgradeOptionInterval } from '../../upgrades/upgrade-options.component' ; import { ConfigsService } from '../../../common/services/configs.service' ; import { PlusService } from '../../plus/plus.service' ; import { ProService } from '../../pro/pro.service' ; import { SupportTier } from './support-tiers.service' ; import { Session } from '../../../services/session' ; import { FormToastService } from '../../../common/services/form-toast.service' ; /**
 * Wire event types
 */ export enum WireEventType { Completed = 1 , Cancelled } /**
 * Wire event
 */ export interface WireEvent { type : WireEventType ; payload ? : any ; } /**
 * Wire reward structure
 */ interface WireReward { id : string ; amount : number ; description : string ; } const buildWireRewardEntries = ( key , data ) : Array < WireReward > => ( data || [ ] ) . map ( entry => ( { id : `${ key }:${ entry . amount }` , amount : entry . amount , description : entry . description , } ) ) . sort ( ( a , b ) => a . amount - b . amount ) ; /**
 * Wire rewards
 */ interface WireRewards { description : string ; count : number ; tokens : Array < WireReward > ; usd : Array < WireReward > ; } /**
 * Upgrade pricing structure
 *  */ export interface WireUpgradePricingOptions { monthly : number ; yearly : number ; lifetime : number ; } export interface WireCurrencyOptions { tokens : boolean ; usd : boolean ; eth : boolean ; btc : boolean ; } const DEFAULT_CURRENCY_OPTIONS_VALUE = { tokens : true , usd : false , eth : false , btc : false , } ; /**
 * Wire types
 */ export type WireType = 'tokens' | 'usd' | 'eth' | 'btc' ; /**
 * Default type value
 */ const DEFAULT_TYPE_VALUE = 'tokens' ; /**
 * Upgrade types
 */ export type WireUpgradeType = 'plus' | 'pro' ; /**
 * Default upgrade type value
 */ const DEFAULT_UPGRADE_TYPE_VALUE = 'plus' ; /**
 * Default isUpgrade flag value
 */ const DEFAULT_IS_UPGRADE_VALUE = false ; /**
 * Default upgrade type value
 */ const DEFAULT_UPGRADE_INTERVAL_VALUE = 'yearly' ; /**
 * Default empty upgrade pricing options
 * (to be populated from configs later)
 */ const DEFAULT_WIRE_UPGRADE_PRICING_OPTIONS = { monthly : 0 , yearly : 0 , lifetime : 0 , } ; /**
 * Wire token types
 */ export type WireTokenType = 'offchain' | 'onchain' ; /**
 * Default token type value
 */ const DEFAULT_TOKEN_TYPE_VALUE = 'offchain' ; /**
 * Default amount value
 */ const DEFAULT_AMOUNT_VALUE = 1 ; /**
 * Default recurring flag value
 */ const DEFAULT_RECURRING_VALUE = false ; /**
 * Default empty wire rewards
 */ const DEFAULT_WIRE_REWARDS_VALUE = { description : '' , count : 0 , tokens : [ ] , usd : [ ] , } ; see { DataArray } interface Data { entityGuid : string ; type : WireType ; supportTier : SupportTier ; upgradeType : WireUpgradeType ; isUpgrade : boolean ; upgradeInterval : UpgradeOptionInterval ; upgradePricingOptions : WireUpgradePricingOptions ; tokenType : WireTokenType ; amount : number ; recurring : boolean ; owner : MindsUser | null ; usdPaymentMethodId : string ; wallet : Wallet ; } see { Data } type DataArray = [ string , WireType , SupportTier , WireUpgradeType , boolean , UpgradeOptionInterval , WireUpgradePricingOptions , WireTokenType , number , boolean , MindsUser , string , Wallet ] ; /**
 * Data validation.
 */ interface DataValidation { isValid : boolean ; isErrorVisible ? : boolean ; error ? : string ; } /**
 * Wire v2 service, using v1 Wire as low-level implementation
 */ @ Injectable ( ) export class WireV2Service implements OnDestroy { /**
     * The entity that's going to receive the payment
     */ readonly entityGuid$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; /**
     * Wire type subject
     */ readonly type$ : BehaviorSubject < WireType > = new BehaviorSubject < WireType > ( DEFAULT_TYPE_VALUE ) ; readonly supportTier$ : BehaviorSubject < SupportTier > = new BehaviorSubject < SupportTier > ( null ) ; /**
     * Wire upgrade type subject
     */ readonly upgradeType$ : BehaviorSubject < WireUpgradeType > = new BehaviorSubject < WireUpgradeType > ( DEFAULT_UPGRADE_TYPE_VALUE ) ; /**
     * Wire type subject
     */ readonly isUpgrade$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( DEFAULT_IS_UPGRADE_VALUE ) ; /**
     * Wire upgrade interval subject
     */ readonly upgradeInterval$ : BehaviorSubject < UpgradeOptionInterval > = new BehaviorSubject < UpgradeOptionInterval > ( DEFAULT_UPGRADE_INTERVAL_VALUE ) ; /**
     * Wire upgrade pricing options subject
     */ readonly upgradePricingOptions$ : BehaviorSubject < WireUpgradePricingOptions > = new BehaviorSubject < WireUpgradePricingOptions > ( DEFAULT_WIRE_UPGRADE_PRICING_OPTIONS ) ; /**
     * Wire upgrade trial observable
     */ readonly upgradeCanHaveTrial$ : Observable < boolean > ; /**
     * Wire token type subject
     */ readonly tokenType$ : BehaviorSubject < WireTokenType > = new BehaviorSubject < WireTokenType > ( DEFAULT_TOKEN_TYPE_VALUE ) ; /**
     * Amount subject
     */ readonly amount$ : BehaviorSubject < number > = new BehaviorSubject < number > ( DEFAULT_AMOUNT_VALUE ) ; /**
     * Recurring flag subject
     */ readonly recurring$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( DEFAULT_RECURRING_VALUE ) ; /**
     * USD payload subject (card selector payment ID)
     */ readonly usdPaymentMethodId$ : BehaviorSubject < string > = new BehaviorSubject < string > ( '' ) ; /**
     * User resolver that's going to asynchronously re-sync the reward tiers (state)
     */ readonly ownerResolver$ : BehaviorSubject < MindsUser | null > = new BehaviorSubject < MindsUser | null > ( null ) ; /**
     * User that's going to receive the payment (might be the same as the entity itself) (state)
     */ readonly owner$ : BehaviorSubject < MindsUser | null > = new BehaviorSubject < MindsUser | null > ( null ) ; /**
     * Wire Rewards (Shop support tiers)
     */ readonly wireRewards$ : BehaviorSubject < WireRewards > = new BehaviorSubject < WireRewards > ( DEFAULT_WIRE_REWARDS_VALUE ) ; /**
     * Sum of the accumulated Wires in the last 30 days, per currency
     */ readonly sums$ : BehaviorSubject < { [ key ] : string ; } > = new BehaviorSubject < { [ key ] : string ; } > ( { } ) ; /**
     * "In Progress" flag subject (state)
     */ readonly inProgress$ : BehaviorSubject < boolean > = new BehaviorSubject < boolean > ( false ) ; protected readonly currencyOptions$ : BehaviorSubject < WireCurrencyOptions > = new BehaviorSubject < WireCurrencyOptions > ( DEFAULT_CURRENCY_OPTIONS_VALUE ) ; /**
     * Validate data observable
     */ readonly validation$ : Observable < DataValidation > ; /**
     * Wire payload observable
     */ protected readonly wirePayload$ : Observable < WireStruc > ; /**
     * Wire payload snapshot
     */ protected wirePayload : WireStruc ; /**
     * Wire snapshot subscription
     */ protected readonly wirePayloadSnapshotSubscription : Subscription ; /**
     * Observer subscription that emits passed User, then refresh its rewards data
     */ protected ownerResolverSubscription : Subscription ; /**
     * Prices for upgrades to Pro/Plus
     */ upgrades : any ; handlers : any ; userIsPlus : boolean ; userIsPro : boolean ; param wallet param api param v1Wire constructor ( public wallet , protected api , protected v1Wire , private plusService , private proService , private session , configs , private toasterSevice ) { this . upgrades = configs . get ( 'upgrades' ) ; this . handlers = configs . get ( 'handlers' ) ; const user = session . getLoggedInUser ( ) ; this . userIsPlus = user && user . plus ; this . userIsPro = user && user . pro ; const wireData$ = combineLatest ( [ this . entityGuid$ , this . type$ , this . supportTier$ , this . upgradeType$ , this . isUpgrade$ , this . upgradeInterval$ , this . upgradePricingOptions$ , this . tokenType$ , this . amount$ , this . recurring$ , this . owner$ . pipe ( distinctUntilChanged ( ) , tap ( owner => { this . currencyOptions$ . next ( { tokens : true , usd : owner && owner . merchant && owner . merchant . id , eth : owner && ! ! owner . eth_wallet , btc : owner && ! ! owner . btc_address , } ) ; } ) ) , this . usdPaymentMethodId$ , this . wallet . wallet$ , ] ) . pipe ( map ( ( [ entityGuid , type , supportTier , upgradeType , isUpgrade , upgradeInterval , upgradePricingOptions , tokenType , amount , recurring , owner , usdPaymentMethodId , wallet , ] ) : Data => ( { entityGuid , type , supportTier , upgradeType , isUpgrade , upgradeInterval , upgradePricingOptions , tokenType , amount , recurring , owner , usdPaymentMethodId , wallet , } ) ) ) ; this . wirePayload$ = wireData$ . pipe ( map ( ( data ) : WireStruc => this . buildWirePayload ( data ) ) ) ; this . validation$ = wireData$ . pipe ( map ( ( data ) : DataValidation => this . validate ( data ) ) ) ; this . wirePayloadSnapshotSubscription = this . wirePayload$ . subscribe ( wirePayload => ( this . wirePayload = wirePayload ) ) ; this . ownerResolverSubscription = this . ownerResolver$ . subscribe ( owner => { this . owner$ . next ( owner ) ; if ( owner && owner . guid ) { this . api . get ( `api/v1/wire/rewards/${ owner . guid }` ) . toPromise ( ) . then ( ( { merchant , eth_wallet , wire_rewards , sums } ) => { const currentOwnerValue = this . owner$ . getValue ( ) ; if ( ! currentOwnerValue || currentOwnerValue . guid !== owner . guid ) { return ; } owner . merchant = merchant ; owner . eth_wallet = eth_wallet ; owner . wire_rewards = wire_rewards ; const tokenRewards = buildWireRewardEntries ( 'tokens' , wire_rewards . rewards . tokens ) ; const usdRewards = buildWireRewardEntries ( 'usd' , wire_rewards . rewards . money ) ; this . wireRewards$ . next ( { description : wire_rewards . description || '' , count : tokenRewards . length + usdRewards . length , tokens : tokenRewards , usd : usdRewards , } ) ; this . owner$ . next ( owner ) ; this . sums$ . next ( sums ) ; } ) ; } } ) ; this . upgradeCanHaveTrial$ = combineLatest ( [ this . upgradeType$ , this . upgradeInterval$ , this . type$ , ] ) . pipe ( map ( ( [ upgradeType , upgradeInterval , paymentType ] ) => { return ( this . upgrades [ upgradeType ] [ upgradeInterval ] . can_have_trial && paymentType === 'usd' ) ; } ) ) ; if ( this . session . isLoggedIn ( ) ) { this . wallet . getTokenAccounts ( ) ; } } /**
     * Destroy lifecycle hook
     */ ngOnDestroy ( ) { if ( this . wirePayloadSnapshotSubscription ) { this . wirePayloadSnapshotSubscription . unsubscribe ( ) ; } if ( this . ownerResolverSubscription ) { this . ownerResolverSubscription . unsubscribe ( ) ; } } param entity setEntity ( entity ) { let guid = '' ; if ( entity && entity . guid ) { guid = entity . guid ; } else if ( entity && entity . entity_guid ) { guid = entity . entity_guid ; } let owner = null ; if ( entity && entity . type === 'user' ) { owner = { ... entity } ; } else if ( entity && entity . ownerObj ) { owner = { ... entity . ownerObj } ; } this . entityGuid$ . next ( guid ) ; this . ownerResolver$ . next ( owner ) ; return this ; } param type setType ( type ) { this . type$ . next ( type ) ; if ( ! this . canRecur ( type , this . tokenType$ . getValue ( ) ) ) { this . recurring$ . next ( false ) ; } if ( this . isUpgrade$ . value ) { if ( type === 'tokens' ) { this . setUpgradeInterval ( 'lifetime' ) ; } else if ( type === 'usd' ) { this . setUpgradeInterval ( 'yearly' ) ; } } this . setUpgradePricingOptions ( type , this . upgradeType$ . getValue ( ) ) ; return this ; } param isUpgrade setIsUpgrade ( isUpgrade ) { this . isUpgrade$ . next ( isUpgrade ) ; if ( this . upgradeInterval$ . value === 'yearly' || this . upgradeInterval$ . value === 'monthly' ) { this . recurring$ . next ( true ) ; } else { this . recurring$ . next ( false ) ; } return this ; } param upgradeType setUpgradeType ( upgradeType ) { this . upgradeType$ . next ( upgradeType ) ; this . setUpgradePricingOptions ( this . type$ . value , upgradeType ) ; return this ; } param upgradeInterval setUpgradeInterval ( upgradeInterval ) { let upgradePrice = this . upgrades [ this . upgradeType$ . value ] [ upgradeInterval ] [ this . type$ . value ] ; if ( upgradeInterval === 'yearly' ) { upgradePrice = upgradePrice ; } this . setAmount ( upgradePrice ) ; this . upgradeInterval$ . next ( upgradeInterval ) ; return this ; } /**
     * Sets the upgrade pricing options for the selected
     * upgrade type and currency
     */ setUpgradePricingOptions ( type , upgradeType ) { if ( this . type$ . value === 'tokens' && this . upgradeInterval$ . value !== 'lifetime' ) { this . upgradeInterval$ . next ( 'lifetime' ) ; } let upgradePricingOptions ; if ( this . isUpgrade$ . value ) { upgradePricingOptions = { monthly : this . upgrades [ upgradeType ] [ 'monthly' ] [ type ] , yearly : this . upgrades [ upgradeType ] [ 'yearly' ] [ type ] , lifetime : this . upgrades [ upgradeType ] [ 'lifetime' ] [ type ] , } ; this . upgradePricingOptions$ . next ( upgradePricingOptions ) ; let upgradePrice = this . upgrades [ this . upgradeType$ . value ] [ this . upgradeInterval$ . value ] [ this . type$ . value ] ; if ( this . upgradeInterval$ . value === 'yearly' ) { upgradePrice = upgradePrice ; } this . setAmount ( upgradePrice ) ; } return this ; } param tokenType setTokenType ( tokenType ) { this . tokenType$ . next ( tokenType ) ; if ( ! this . canRecur ( this . type$ . getValue ( ) , tokenType ) ) { this . recurring$ . next ( false ) ; } return this ; } param amount setAmount ( amount ) { this . amount$ . next ( amount ) ; return this ; } param recurring setRecurring ( recurring ) { const canRecur = this . canRecur ( this . type$ . getValue ( ) , this . tokenType$ . getValue ( ) ) || this . isUpgrade$ . getValue ( ) ; this . recurring$ . next ( canRecur && recurring ) ; return this ; } param inProgress setInProgress ( inProgress ) { this . inProgress$ . next ( inProgress ) ; return this ; } param paymentMethodId setUsdPaymentMethodId ( paymentMethodId ) { this . usdPaymentMethodId$ . next ( paymentMethodId ) ; return this ; } /**
     * Reset the behaviors to its original subjects
     */ reset ( ) { this . setType ( DEFAULT_TYPE_VALUE ) ; this . setTokenType ( DEFAULT_TOKEN_TYPE_VALUE ) ; this . setAmount ( DEFAULT_AMOUNT_VALUE ) ; this . setRecurring ( DEFAULT_RECURRING_VALUE ) ; this . setInProgress ( false ) ; return this ; } param type param tokenType canRecur ( type , tokenType ) { return ( type === 'tokens' && tokenType === 'offchain' ) || type === 'usd' ; } param data protected validate ( data ) { const valid = ( ) : DataValidation => ( { isValid : true } ) ; const invalid = ( error ? , isErrorVisible ? ) => ( { isValid : false , error , isErrorVisible , } ) ; if ( ! data || ! data . entityGuid ) { return invalid ( ) ; } if ( this . isUpgrade$ . getValue ( ) ) { if ( this . upgradeType$ . getValue ( ) === 'pro' && this . userIsPro ) { return invalid ( 'You are already a Pro member' , true ) ; } if ( this . upgradeType$ . getValue ( ) === 'plus' && this . userIsPlus ) { return invalid ( 'You are already a Minds+ member' , true ) ; } } if ( this . supportTier$ . getValue ( ) && this . supportTier$ . getValue ( ) . subscription_urn ) { return invalid ( 'You are already a member' , true ) ; } if ( data . amount <= 0 ) { return invalid ( 'Amount should be greater than zero' , false ) ; } const username = data . owner ? `@${ data . owner . username }` : 'This channel' ; switch ( data . type ) { case 'tokens' : case 'eth' : if ( data . type === 'tokens' && data . tokenType === 'offchain' ) { const isUpgrade = data . entityGuid === this . handlers . pro || data . entityGuid === this . handlers . plus ; const amountExceedsLimit = ! isUpgrade && data . amount > data . wallet . limits . wire ; if ( data . wallet . loaded && amountExceedsLimit ) { return invalid ( `Cannot spend more than ${ data . wallet . limits . wire } tokens today` , true ) ; } else if ( data . wallet . loaded && data . amount > data . wallet . offchain . balance ) { return invalid ( `Cannot spend more than ${ data . wallet . offchain . balance } tokens` , true ) ; } } else { if ( ! data . owner || ! data . owner . eth_wallet ) { return invalid ( `${ username } has not configured their Ethereum wallet yet` , true ) ; } } break ; case 'usd' : if ( ! data . owner || ! data . owner . merchant || ! data . owner . merchant . id ) { return invalid ( `${ username } is not able to receive USD at the moment` , true ) ; } break ; case 'btc' : if ( ! data . owner || ! data . owner . btc_address ) { return invalid ( `${ username } has not configured their Bitcoin address yet` , true ) ; } break ; } return valid ( ) ; } param data protected buildWirePayload ( data ) { const wire = { guid : data . entityGuid , amount : data . amount , recurring : Boolean ( this . canRecur ( data . type , data . tokenType ) && data . recurring ) , } ; switch ( data . type ) { case 'tokens' : wire . payloadType = data . tokenType ; if ( data . tokenType === 'offchain' ) { wire . payload = { } ; } else if ( data . tokenType === 'onchain' ) { wire . payload = { receiver : data . owner && data . owner . eth_wallet , address : '' , } ; } break ; case 'usd' : wire . payloadType = 'usd' ; wire . payload = { paymentMethodId : data . usdPaymentMethodId , } ; break ; case 'eth' : wire . payloadType = 'eth' ; wire . payload = { receiver : data . owner && data . owner . eth_wallet , address : '' , } ; break ; case 'btc' : wire . payloadType = 'btc' ; wire . payload = { receiver : data . owner && data . owner . btc_address , } ; break ; } if ( data . isUpgrade && data . upgradeInterval ) { wire . recurringInterval = data . upgradeInterval ; } return wire as WireStruc ; } /**
     * Submits the Wire
     */ async submit ( ) { if ( ! this . wirePayload ) { throw new Error ( `There's nothing to send` ) ; } this . inProgress$ . next ( true ) ; try { const response = await this . v1Wire . submitWire ( this . wirePayload ) ; this . inProgress$ . next ( false ) ; return response ; } catch ( e ) { this . inProgress$ . next ( false ) ; throw e ; } } }