// License: LGPL-3.0-or-later const h = require('snabbdom/h') const R = require('ramda') const flyd = require('flyd') flyd.lift = require('flyd/module/lift') flyd.flatMap = require('flyd/module/flatmap') const request = require('../../common/request') const cardForm = require('../../components/card-form.es6') const sepaForm = require('../../components/sepa-form.es6') const format = require('../../common/format') const progressBar = require('../../components/progress-bar') const sepaTab = 'sepa' const cardTab = 'credit_card' function init(state) { const payload$ = flyd.map(supp => ({card: {holder_id: supp.id, holder_type: 'Supporter'}}), state.supporter$) const supporterID$ = flyd.map(supp => supp.id, state.supporter$) const card$ = flyd.merge( flyd.stream({}) , flyd.map(supp => ({name: supp.name, address_zip: supp.zip_code}), state.supporter$)) state.cardForm = cardForm.init({ path: '/cards', card$, payload$ }) state.sepaForm = sepaForm.init({ supporter: supporterID$ } ) // Set the card ID into the donation object when it is saved const cardToken$ = flyd.map(R.prop('token'), state.cardForm.saved$) const donationWithCardToken$ = flyd.lift(R.assoc('token'), cardToken$, state.donation$) // Set the sepa transfer details ID into the donation object when it is saved const sepaId$ = flyd.map(R.prop('id'), state.sepaForm.saved$) const donationWithSepaId$ = flyd.lift(R.assoc('direct_debit_detail_id'), sepaId$, state.donation$) state.donationParams$ = flyd.immediate( flyd.combine((sepaParams, cardParams, activeTab) => { if(activeTab() == sepaTab) { return sepaParams() } else if(activeTab() == cardTab) { return cardParams() } }, [donationWithSepaId$, donationWithCardToken$, state.activePaymentTab$]) ) const donationResp$ = flyd.flatMap(postDonation, state.donationParams$) state.error$ = flyd.mergeAll([ flyd.map(R.prop('error'), flyd.filter(resp => resp.error, donationResp$)) , flyd.map(R.always(undefined), state.cardForm.form.submit$) , flyd.map(R.always(undefined), state.sepaForm.form.submit$) , state.cardForm.error$ , state.sepaForm.error$ ]) state.paid$ = flyd.filter(resp => !resp.error, donationResp$) // Control progress bar for card payment state.progress$ = flyd.scanMerge([ [state.cardForm.form.validSubmit$, R.always({status: I18n.t('nonprofits.donate.payment.loading.checking_card'), percentage: 20})] , [state.cardForm.saved$, R.always({status: I18n.t('nonprofits.donate.payment.loading.sending_payment'), percentage: 100})] , [state.cardForm.error$, R.always({hidden: true})] // Hide when an error shows up , [flyd.filter(R.identity, state.error$), R.always({hidden: true})] // Hide when an error shows up ], {hidden: true}) state.loading$ = flyd.mergeAll([ flyd.map(R.always(true), state.cardForm.form.validSubmit$) , flyd.map(R.always(true), state.sepaForm.form.validSubmit$) , flyd.map(R.always(false), state.paid$) , flyd.map(R.always(false), state.cardForm.error$) , flyd.map(R.always(false), state.sepaForm.error$) , flyd.map(R.always(false), state.error$) ]) // Post the gift option, if necessary const paramsWithGift$ = flyd.filter(params => params.gift_option_id || params.gift_option && params.gift_option.id, state.params$) const paidWithGift$ = flyd.lift(R.pair, paramsWithGift$, state.paid$) flyd.map( R.apply((params, result) => postGiftOption(params.gift_option_id || params.gift_option.id, result)) , paidWithGift$ ) // post utm tracking details after donation is saved flyd.map( R.apply((utmParams, donationResponse) => postTracking(app.utmParams, donationResp$)) , state.paid$ ) return state } const postGiftOption = (campaign_gift_option_id, result) => { return flyd.map(R.prop('body'), request({ path: '/campaign_gifts' , method: 'post' , send: {campaign_gift: {donation_id: result.json ? result.json.donation.id // for recurring : result.donation.id // for one-time , campaign_gift_option_id}} }).load) } const postTracking = (utmParams, donationResponse) => { const params = R.merge(utmParams, {donation_id: donationResponse().donation.id}) if(utmParams.utm_source || utmParams.utm_medium || utmParams.utm_content || utmParams.utm_campaign) { return flyd.map(R.prop('body'), request({ path: `/nonprofits/${app.nonprofit_id}/tracking` , method: 'post' , send: params }).load) } } var posting = false // hack switch to prevent any kind of charge double post // Post either a recurring or one-time donation const postDonation = (donation) => { if(posting) return flyd.stream() else posting = true var prefix = `/nonprofits/${app.nonprofit_id}/` var postfix = donation.recurring ? 'recurring_donations' : 'donations' if(donation.weekly) { donation.amount = Math.round(4.3 * donation.amount); } delete donation.weekly; // needs to be removed to be processed if(donation.recurring) donation = {recurring_donation: donation} return flyd.map(R.prop('body'), request({ path: prefix + postfix , method: 'post' , send: donation }).load) } const paymentTabs = (state) => { if(state.activePaymentTab$() == sepaTab) { return payWithSepaTab(state) } else if(state.activePaymentTab$() == cardTab) { return payWithCardTab(state) } } const payWithSepaTab = state => { return h('div.u-marginBottom--10', [ sepaForm.view(state.sepaForm) ]) } const payWithCardTab = state => { return h('div.u-marginBottom--10', [ cardForm.view(R.merge(state.cardForm, {error$: state.error$, hideButton: state.loading$()})) , progressBar(state.progress$()) ]) } function view(state) { var isRecurring = state.donation$().recurring var dedic = state.dedicationData$() var amountLabel = isRecurring ? ` ${I18n.t('nonprofits.donate.payment.monthly_recurring')}` : ` ${I18n.t('nonprofits.donate.payment.one_time')}` var weekly=""; if (state.donation$().weekly) { amountLabel = amountLabel.replace(I18n.t('nonprofits.donate.amount.monthly'),I18n.t('nonprofits.donate.amount.weekly')) + "*"; weekly= h('div.u-centered.notice',[h("small",I18n.t('nonprofits.donate.amount.weekly_notice',{amount:(format.weeklyToMonthly(state.donation$().amount)/100.0),currency:app.currency_symbol}))]); } return h('div.wizard-step.payment-step', [ h('p.u-fontSize--18 u.marginBottom--0.u-centered.amount', [ h('span', app.currency_symbol + format.centsToDollars(state.donation$().amount)) , h('strong', amountLabel) ]) , weekly , dedic && (dedic.first_name || dedic.last_name) ? h('p.u-centered', `${dedic.dedication_type === 'memory' ? I18n.t('nonprofits.donate.dedication.in_memory_label') : I18n.t('nonprofits.donate.dedication.in_honor_label')} ` + `${dedic.first_name || ''} ${dedic.last_name || ''}`) : '' , paymentTabs(state) ]) } module.exports = {view, init}