Add createPaymentRequestHandler
[?]
Feb 6, 2021, 9:36 PM
KKJSBWO6RNORAPTJPCCUJJNVI2OYTGLQKB3XJGOASH43GNTJBMKACDependencies
- [2]
XXJFUZOVAdd first revenue date to project payout computation. - [3]
KET5QGQPAdd billable list (in-progress) - [4]
YBLHJFCNImplement billing modal. - [5]
3PFXXJTLWIP - [6]
SQ7UMLN5Get z-addr checks working. - [7]
T2DN23M7Factor out billing create component. - [8]
4354Y4PEAdd endpoint to list project contributors. - [9]
IR75ZMX3Return actual events for interval ends, not just timestamps. - [10]
N6FG4EW6Working bootstrap modal! Only a little FFI. - [11]
ANDJ6GEYAdd billing component skeleton. - [12]
IPG33FAWAdd billing daemon - [13]
M4PWY5RUPreliminary work to add support for Zcash payments. - [14]
GCVQD44VCreate amends endpoint, switch to UUID primary keys - [15]
O227CEAVAdds storage of original event JSON for some DBOp constructors. - [16]
RFYEVKZQAdd nix-shell based build environment. - [17]
DZ7G36NCAllow first-revenue cutoff for depreciation. - [18]
UWMGUJOWAutoformat sources. - [19]
DFOBMSAOInitial work on payments API - [20]
SEWTRB6SImplement payment request creation functions. - [21]
4R7XIYK3Switch from ClassyPrelude to Relude - [22]
DJATFGICSupport client builds in nix-shell --pure. - [23]
27H4DECZAdd billing create API call. - [24]
BSIUHCGFAdd payment response handler. - [25]
AL37SVTCImplement payments service endpoints. - [26]
4GOBY5NQWIP on modals. - [27]
B6HWAPDPModularize & update to recent haskoin. - [28]
U7YAT2ZKAdd error reporting to signup form. - [29]
EFSXYZPOAutoformat everything with brittany. - [30]
X3ES7NUAFine. I'll use ormolu. At least it doesn't break the code. - [*]
VTZT2ILUWire up billing navigation. - [*]
W35DDBFYFactor common JSON conversions up into client lib module. - [*]
NVOCQVASInitial failing tests. - [*]
KEP5WUFJConvert project to stack-based build.
Change contents
- edit in client/src/Aftok/Api/Billing.purs at line 9
import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT) - replacement in client/src/Aftok/Api/Billing.purs at line 26
import Data.Traversable (traverse)import Data.Traversable (traverse, sequence) - edit in client/src/Aftok/Api/Billing.purs at line 69
instance showRecurrence :: Show Recurrence whereshow = case _ ofAnnually -> "Annually"Monthly i -> "Monthly " <> show iWeekly i -> "Weekly " <> show iOneTime -> "OneTime"recurrenceStr :: Recurrence -> StringrecurrenceStr = case _ ofAnnually -> "Annually"Monthly i -> "Every " <> show i <> " months"Weekly i -> "Every " <> show i <> " weeks"OneTime -> "One-time purchase" - replacement in client/src/Aftok/Api/Billing.purs at line 119
let annually = traverse (map \(_ :: Unit) -> Annually) (obj .:? "annually")monthly = traverse (map Monthly) (obj .:? "monthly")weekly = traverse (map Weekly) (obj .:? "weekly")let parseInner f outer inner = map f ((MaybeT <<< (_ .:? inner)) =<< MaybeT (obj .:? outer))annually = traverse (map \(_ :: Unit) -> Annually) (obj .:? "annually")monthly = sequence $ runMaybeT (parseInner Monthly "monthly" "months")weekly = sequence $ runMaybeT (parseInner Weekly "weekly" "weeks") - edit in client/src/Aftok/Api/Billing.purs at line 125
- edit in client/src/Aftok/Api/Billing.purs at line 126
- replacement in client/src/Aftok/Api/Billing.purs at line 133
recurrence :: Recurrence <- parseRecurrence =<< bobj .: "recurrence"recurrence <- parseRecurrence =<< bobj .: "recurrence" - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 3
{--<div role="document" class="ant-modal" style="width: 520px; transform-origin: 724px 147.062px;"><div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;">sentinelStart</div><div class="ant-modal-content"><button aria-label="Close" class="ant-modal-close"><span class="ant-modal-close-x"><i aria-label="icon: close" class="anticon anticon-close ant-modal-close-icon"><svg viewBox="64 64 896 896" class="" data-icon="close" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"></path></svg></i></span></button><div class="ant-modal-header"><div class="ant-modal-title" id="rcDialogTitle0">Tip a proposal</div></div><div class="ant-modal-body"><div class="TipJarModal"><div class="TipJarModal-uri"><div><div class="TipJarModal-uri-qr"><span style="opacity: 1;"><canvas height="128" width="128" style="height: 128px; width: 128px;"></canvas></span></div></div><div class="TipJarModal-uri-info"><div class="ant-row ant-form-item TipJarModal-uri-info-input CopyInput"><div class="ant-form-item-label"><label class="" title="Amount">Amount</label></div><div class="ant-form-item-control-wrapper"><div class="ant-form-item-control"><span class="ant-form-item-children"><span class="ant-input-group-wrapper"><span class="ant-input-wrapper ant-input-group"><input type="number" placeholder="Amount to send" class="ant-input" value="0.2"><span class="ant-input-group-addon">ZEC</span></span></span></span></div></div></div><div class="ant-row ant-form-item CopyInput TipJarModal-uri-info-input is-textarea"><div class="ant-form-item-label"><label class="" title="Payment URI">Payment URI</label></div><div class="ant-form-item-control-wrapper"><div class="ant-form-item-control"><span class="ant-form-item-children"><textarea readonly="" rows="3" class="ant-input">zcash:zs1xzymv205x8hhn8kt3pu43c6knjlelvxfgzgsyyus9yxhmdvqeu0yj0m2knzd3p93slsygkp94rz?amount=0.2</textarea><button type="button" class="ant-btn ant-btn-icon-only"><i aria-label="icon: copy" class="anticon anticon-copy"><svg viewBox="64 64 896 896" class="" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path></svg></i></button></span></div></div></div><a href="zcash:zs1xzymv205x8hhn8kt3pu43c6knjlelvxfgzgsyyus9yxhmdvqeu0yj0m2knzd3p93slsygkp94rz?amount=0.2" class="ant-btn ant-btn-ghost ant-btn-lg ant-btn-block"><span>Open in Wallet </span><i aria-label="icon: link" class="anticon anticon-link"><svg viewBox="64 64 896 896" class="" data-icon="link" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M574 665.4a8.03 8.03 0 0 0-11.3 0L446.5 781.6c-53.8 53.8-144.6 59.5-204 0-59.5-59.5-53.8-150.2 0-204l116.2-116.2c3.1-3.1 3.1-8.2 0-11.3l-39.8-39.8a8.03 8.03 0 0 0-11.3 0L191.4 526.5c-84.6 84.6-84.6 221.5 0 306s221.5 84.6 306 0l116.2-116.2c3.1-3.1 3.1-8.2 0-11.3L574 665.4zm258.6-474c-84.6-84.6-221.5-84.6-306 0L410.3 307.6a8.03 8.03 0 0 0 0 11.3l39.7 39.7c3.1 3.1 8.2 3.1 11.3 0l116.2-116.2c53.8-53.8 144.6-59.5 204 0 59.5 59.5 53.8 150.2 0 204L665.3 562.6a8.03 8.03 0 0 0 0 11.3l39.8 39.8c3.1 3.1 8.2 3.1 11.3 0l116.2-116.2c84.5-84.6 84.5-221.5 0-306.1zM610.1 372.3a8.03 8.03 0 0 0-11.3 0L372.3 598.7a8.03 8.03 0 0 0 0 11.3l39.6 39.6c3.1 3.1 8.2 3.1 11.3 0l226.4-226.4c3.1-3.1 3.1-8.2 0-11.3l-39.5-39.6z"></path></svg></i></a></div></div><div class="TipJarModal-fields"><div class="TipJarModal-fields-row"><div class="ant-row ant-form-item CopyInput TipJarModal-fields-row-address"><div class="ant-form-item-label"><label class="" title="Address">Address</label></div><div class="ant-form-item-control-wrapper"><div class="ant-form-item-control"><span class="ant-form-item-children"><input readonly="" type="text" class="ant-input" value="zs1xzymv205x8hhn8kt3pu43c6knjlelvxfgzgsyyus9yxhmdvqeu0yj0m2knzd3p93slsygkp94rz"><button type="button" class="ant-btn ant-btn-icon-only"><i aria-label="icon: copy" class="anticon anticon-copy"><svg viewBox="64 64 896 896" class="" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path></svg></i></button></span></div></div></div></div><div class="TipJarModal-fields-row"><div class="ant-row ant-form-item ant-form-item-with-help CopyInput"><div class="ant-form-item-label"><label class="" title="Zcash CLI command">Zcash CLI command</label></div><div class="ant-form-item-control-wrapper"><div class="ant-form-item-control"><span class="ant-form-item-children"><input readonly="" type="text" class="ant-input" value="zcash-cli z_sendmany YOUR_ADDRESS '[{"address":"zs1xzymv205x8hhn8kt3pu43c6knjlelvxfgzgsyyus9yxhmdvqeu0yj0m2knzd3p93slsygkp94rz","amount":0.2}]'"><button type="button" class="ant-btn ant-btn-icon-only"><i aria-label="icon: copy" class="anticon anticon-copy"><svg viewBox="64 64 896 896" class="" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path></svg></i></button></span><div class="ant-form-explain">Make sure you replace YOUR_ADDRESS with your actual address</div></div></div></div></div></div></div></div><div class="ant-modal-footer"><button type="button" class="ant-btn ant-btn-primary"><span>Done</span></button></div></div><div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;">sentinelEnd</div></div>import Preludeimport Control.Monad.Trans.Class (lift)-- import Data.DateTime (DateTime, date)import Data.Either (Either(..), note)import Data.Fixed as Fixedimport Data.Foldable (any)import Data.Int as Intimport Data.Maybe (Maybe(..), maybe)import Data.Newtype (unwrap)import Data.Number (fromString) as Numberimport Data.Number.Format (toString) as Number-- import Data.Unfoldable as Uimport Data.Validation.Semigroup (V(..), toEither)import Data.Time.Duration (Hours(..), Days(..))-- import Data.Traversable (traverse)import Data.Tuple (Tuple(..))import Effect.Aff (Aff)-- import Effect.Class (liftEffect)-- import Effect.Now (nowDateTime)import Halogen as Himport Halogen.HTML.Core (ClassName(..))import Halogen.HTML as HHimport Halogen.HTML.Events as Eimport Halogen.HTML.Properties as Pimport Aftok.Types (System, ProjectId)import Aftok.HTML.Classes as Cimport Aftok.Modals as Modalsimport Aftok.Modals.ModalFFI as ModalFFIimport Aftok.Api.Types (APIError(..))import Aftok.Api.Billing( BillableId, Billable, Recurrence(..), createBillable)import Aftok.Zcash (ZEC(..), toZatoshi)data Field= NameField| DescField| MessageField| MonthlyRecurrenceField| WeeklyRecurrenceField| AmountField| GracePeriodField| RequestExpiryFieldderive instance fieldEq :: Eq Fieldderive instance fieldOrd :: Ord Fielddata RType= RTAnnual| RTMonthly| RTWeekly| RTOneTimederive instance rtypeEq :: Eq RTypetype CState ={ projectId :: ProjectId, name :: Maybe String, description :: Maybe String, message :: Maybe String, recurrenceType :: RType, recurrenceValue :: Maybe Int, amount :: Maybe ZEC, gracePeriod :: Maybe Days, requestExpiry :: Maybe Hours, fieldErrors :: Array Field}data Query a= Tell atype Input = ProjectIdtype Output = Tuple BillableId Billabledata Action= ProjectChanged ProjectId| SetName String| SetDesc String| SetMessage String| SetRecurrenceType RType| SetRecurrenceMonths String| SetRecurrenceWeeks String| SetBillingAmount String| SetGracePeriod String| SetRequestExpiry String| SaveBillabletype Slot id= H.Slot Query Output idtype Capability (m :: Type -> Type)= { createBillable :: ProjectId -> Billable -> m (Either APIError BillableId)}component ::forall m.Monad m =>System m ->Capability m ->H.Component HH.HTML Query Input Output mcomponent system caps =H.mkComponent{ initialState, render, eval:H.mkEval$ H.defaultEval{ handleAction = eval, receive = Just <<< ProjectChanged}}whereinitialState :: Input -> CStateinitialState input ={ projectId: input, name : Nothing, description : Nothing, message : Nothing, recurrenceType : RTOneTime, recurrenceValue : Nothing, amount : Nothing, gracePeriod : Nothing, requestExpiry : Nothing, fieldErrors : []} - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 133
--}[4.19122]render :: forall slots. CState -> H.ComponentHTML Action slots mrender st =Modals.modalWithSave "createBillable" "Create Billable" SaveBillable[ HH.form_[ formGroup st[ NameField ][ HH.label[ P.for "billableName"][ HH.text "Product Name" ], HH.input[ P.type_ P.InputText, P.classes [ C.formControlSm ], P.id_ "billableName", P.placeholder "A name for the product or service you want to bill for", E.onValueInput (Just <<< SetName)]], formGroup st[ DescField ][ HH.label[ P.for "billableDesc"][ HH.text "Product Description" ], HH.input[ P.type_ P.InputText, P.classes [ C.formControlSm ], P.id_ "billableDesc", P.placeholder "Description of the product or service", E.onValueInput (Just <<< SetDesc)]], formGroup st[ MessageField ][ HH.label[ P.for "billableMsg"][ HH.text "Message to be included with bill" ], HH.input[ P.type_ P.InputText, P.id_ "billableMsg", P.placeholder "Enter your message here", E.onValueInput (Just <<< SetMessage)]], formGroup st[MonthlyRecurrenceField, WeeklyRecurrenceField][ HH.label_[ HH.input([ P.type_ P.InputRadio, P.name "recurType", E.onClick \_ -> Just (SetRecurrenceType RTAnnual)] <> (if st.recurrenceType == RTAnnual then [P.checked true] else [])), HH.text " Annual"], HH.label_[ HH.input([ P.type_ P.InputRadio, P.name "recurType", E.onClick \_ -> Just (SetRecurrenceType RTMonthly)] <> (if st.recurrenceType == RTMonthly then [P.checked true] else [])), HH.text " every ", HH.input[ P.type_ P.InputNumber, P.classes [ C.formControlSm ], P.value (if st.recurrenceType == RTMonthlythen maybe "" show st.recurrenceValueelse ""), P.min 1.0, P.max 12.0, E.onValueInput (Just <<< SetRecurrenceMonths)], HH.text " Months"], HH.label_[ HH.input([ P.type_ P.InputRadio, P.name "recurType", E.onClick \_ -> Just (SetRecurrenceType RTWeekly)] <> (if st.recurrenceType == RTWeekly then [P.checked true] else [])), HH.text " every ", HH.input[ P.type_ P.InputNumber, P.classes [ C.formControlSm ], P.value (if st.recurrenceType == RTWeeklythen maybe "" show st.recurrenceValueelse ""), P.min 1.0, P.max 12.0, E.onValueInput (Just <<< SetRecurrenceWeeks)], HH.text " Weeks"], HH.label_[ HH.input([ P.type_ P.InputRadio, P.name "recurType", E.onClick \_ -> Just (SetRecurrenceType RTOneTime)] <> (if st.recurrenceType == RTOneTime then [P.checked true] else [])), HH.text " One-Time"]], formGroup st[AmountField][ HH.label[ P.for "billableAmount"][ HH.text "Amount" ], HH.input[ P.type_ P.InputNumber, P.classes [ C.formControlSm ], P.id_ "billableAmount", P.value (maybe "" (Fixed.toString <<< unwrap) st.amount), P.placeholder "1.0", P.min 0.0, E.onValueInput (Just <<< SetBillingAmount)], HH.div[ P.classes [ ClassName "input-group-append" ] ][ HH.span [ P.classes [ ClassName "input-group-text" ] ] [ HH.text "ZEC" ] ]], formGroup st[GracePeriodField][ HH.label[ P.for "gracePeriod"][ HH.text "Grace Period (Days)" ], HH.input[ P.type_ P.InputNumber, P.id_ "gracePeriod", P.classes [ C.formControlSm ], P.value (maybe "" (Number.toString <<< unwrap) st.gracePeriod), P.placeholder "Days until a bill is considered overdue", P.min 0.0, E.onValueInput (Just <<< SetGracePeriod)]], formGroup st[RequestExpiryField][ HH.label[ P.for "requestExpiry"][ HH.text "Request Expiry Period (Hours)" ], HH.input[ P.type_ P.InputNumber, P.id_ "gracePeriod", P.classes [ C.formControlSm ], P.value (maybe "" (Number.toString <<< unwrap) st.requestExpiry), P.placeholder "Hours until a payment request expires", P.min 0.0, E.onValueInput (Just <<< SetRequestExpiry)]]]]formGroup :: forall i a. CState -> Array Field -> Array (HH.HTML i a) -> HH.HTML i aformGroup st fields body =HH.div[ P.classes [C.formGroup] ](body <> (fieldError st =<< fields))fieldError :: forall i a. CState -> Field -> Array (HH.HTML i a)fieldError st field =if any (_ == field) st.fieldErrorsthen case field ofNameField -> err "The name field is required"DescField -> err "The description field is required"MessageField -> err "The message field is required"MonthlyRecurrenceField -> err "You must enter a valid number of months."WeeklyRecurrenceField -> err "You must enter a valid number of weeks."AmountField -> err "You must enter a valid amount of ZEC"GracePeriodField -> err "You must enter a valid number of hours."RequestExpiryField -> err "You must enter a valid number of hours."else []whereerr str = [ HH.div_ [ HH.span [ P.classes (ClassName <$> [ "badge", "badge-danger-soft" ]) ] [ HH.text str ] ] ]eval :: forall slots. Action -> H.HalogenM CState Action slots Output m Uniteval = case _ ofProjectChanged pid ->H.modify_ (_ { projectId = pid })SetName name ->H.modify_ (_ { name = Just name })SetDesc desc ->H.modify_ (_ { description = Just desc })SetMessage msg ->H.modify_ (_ { message = Just msg })SetRecurrenceType rtype -> docurRecurType <- H.gets _.recurrenceTypecurDuration <- H.gets _.recurrenceValuelet rdur = case curRecurType ofRTMonthly | rtype == RTMonthly -> curDurationRTWeekly | rtype == RTWeekly -> curDuration_ -> NothingH.modify_ (_ { recurrenceType = rtype, recurrenceValue = rdur })SetRecurrenceMonths dur ->case Int.fromString dur of(Just n) -> H.modify_ (_ { recurrenceType = RTMonthly, recurrenceValue = Just n })(Nothing) -> pure unitSetRecurrenceWeeks dur ->case Int.fromString dur of(Just n) -> H.modify_ (_ { recurrenceType = RTWeekly, recurrenceValue = Just n })(Nothing) -> pure unitSetBillingAmount amt ->case Fixed.fromString amt of(Just zec) -> H.modify_ (_ { amount = Just (ZEC zec) })(Nothing) -> pure unitSetGracePeriod dur ->case Number.fromString dur of(Just n) -> H.modify_ (_ { gracePeriod = Just (Days n) })(Nothing) -> pure unitSetRequestExpiry dur ->case Number.fromString dur of(Just n) -> H.modify_ (_ { requestExpiry = Just (Hours n) })(Nothing) -> pure unitSaveBillable -> donameV <- V <<< note [NameField] <$> H.gets (_.name)descV <- V <<< note [DescField] <$> H.gets (_.description)msgV <- V <<< note [MessageField] <$> H.gets (_.message)rtype <- H.gets (_.recurrenceType)rvalueV <- case rtype ofRTAnnual -> pure $ V (Right Annually)RTMonthly -> V <<< maybe (Left [MonthlyRecurrenceField]) (Right <<< Monthly) <$> H.gets (_.recurrenceValue)RTWeekly -> V <<< maybe (Left [WeeklyRecurrenceField]) (Right <<< Weekly) <$> H.gets (_.recurrenceValue)RTOneTime -> pure $ V (Right OneTime)zatsV <- V <<< maybe (Left [AmountField]) (Right <<< toZatoshi) <$> H.gets (_.amount)gperV <- V <<< note [GracePeriodField] <$> H.gets (_.gracePeriod)expiryV <- V <<< note [RequestExpiryField] <$> H.gets (_.requestExpiry)let toBillable = { name: _, description: _, message: _, recurrence: _, amount: _, gracePeriod: _, expiryPeriod: _}reqV :: V (Array Field) BillablereqV =toBillable <$> nameV<*> descV<*> msgV<*> rvalueV<*> zatsV<*> gperV<*> expiryVcase toEither reqV ofLeft errors -> doH.modify_ (_ { fieldErrors = errors })Right billable -> dopid <- H.gets (_.projectId)res <- lift $ caps.createBillable pid billablecase res ofRight bid -> doH.raise (Tuple bid billable)lift $ system.toggleModal "createBillable" ModalFFI.HideModalLeft errs ->lift $ system.error (show errs)apiCapability :: Capability AffapiCapability ={ createBillable: createBillable}mockCapability :: Capability AffmockCapability ={ createBillable: \_ _ -> pure $ Left Forbidden } - replacement in client/src/Aftok/Billing.purs at line 13
import Data.Traversable (traverse)import Data.Traversable (traverse_) - edit in client/src/Aftok/Billing.purs at line 24
import Aftok.Billing.PaymentRequest as PaymentRequest - edit in client/src/Aftok/Billing.purs at line 36
, recurrenceStr - edit in client/src/Aftok/Billing.purs at line 62
, createPaymentRequest :: PaymentRequest.Slot Unit - edit in client/src/Aftok/Billing.purs at line 67
_createPaymentRequest = SProxy :: SProxy "createPaymentRequest" - replacement in client/src/Aftok/Billing.purs at line 127
, Modals.modalButton "createBillable" "Create billable", HH.div[ P.classes (ClassName <$> [ "col-md-2" ]) ][ Modals.modalButton "createBillable" "Create billable" ], system.portal_createBillableunit(Create.component system caps.createBillable)(unwrap p).projectIdNothing(Just <<< BillableCreated) - edit in client/src/Aftok/Billing.purs at line 175
, colmd2 (Just (recurrenceStr b.recurrence)), HH.div[ P.classes (ClassName <$> [ "col-md-2" ]) ][ Modals.modalButton "createPaymentRequest" "New Payment Request" ] - replacement in client/src/Aftok/Billing.purs at line 190
refreshBillables currentProjecttraverse_ refreshBillables currentProject - replacement in client/src/Aftok/Billing.purs at line 194
refreshBillables currentProjectrefreshBillables p - replacement in client/src/Aftok/Billing.purs at line 202
refreshBillables currentProjecttraverse_ refreshBillables currentProject - replacement in client/src/Aftok/Billing.purs at line 205
billables <- lift $ traverse (caps.listProjectBillables <<< (_.projectId) <<< unwrap) currentProjectbillables <- lift $ caps.listProjectBillables (unwrap currentProject).projectId - replacement in client/src/Aftok/Billing.purs at line 207
Nothing -> pure unitJust (Left err) -> lift $ system.error (show err)Just (Right b) -> H.modify_ (_ { billables = b })Left err -> lift $ system.error (show err)Right b -> H.modify_ (_ { billables = b }) - replacement in lib/Aftok/Database/PostgreSQL/Projects.hs at line 42
( Email (..),( DepreciationRules (..),Email (..), - replacement in lib/Aftok/Database/PostgreSQL/Projects.hs at line 49
DepreciationRules(..),depfdepf, - replacement in lib/Aftok/Database/PostgreSQL/Projects.hs at line 67
<*> (DepreciationRules<$> (unSerDepFunction <$> fieldWith fromJSONField)<*> (fmap C.toThyme <$> field)<*> ( DepreciationRules<$> (unSerDepFunction <$> fieldWith fromJSONField)<*> (fmap C.toThyme <$> field) - replacement in lib/Aftok/Json.hs at line 196
recurrenceJSON' (B.Monthly i) = object ["monthly " .= object ["months" .= i]]recurrenceJSON' (B.Monthly i) = object ["monthly" .= object ["months" .= i]] - replacement in lib/Aftok/Json.hs at line 198
recurrenceJSON' (B.Weekly i) = object ["weekly " .= object ["weeks" .= i]]recurrenceJSON' (B.Weekly i) = object ["weekly" .= object ["weeks" .= i]] - replacement in lib/Aftok/TimeLog.hs at line 20
DepreciationRules(..),DepreciationRules (..), - replacement in lib/Aftok/TimeLog.hs at line 142
toDepF (DepreciationRules (LinearDepreciation undepLength depLength) firstRevenue) =toDepF (DepreciationRules (LinearDepreciation undepLength depLength) firstRevenue) = - replacement in lib/Aftok/Types.hs at line 66
data DepreciationRules = DepreciationRules{ _depf :: DepreciationFunction, _firstRevenue :: Maybe C.UTCTime}data DepreciationRules= DepreciationRules{ _depf :: DepreciationFunction,_firstRevenue :: Maybe C.UTCTime} - edit in server/Aftok/Snaplet/Billing.hs at line 7
createPaymentRequestHandler, - replacement in server/Aftok/Snaplet/Billing.hs at line 11
import Aftok.Billing as Bimport Aftok.Billing( Billable,Billable' (..),BillableId (..),Recurrence (..),SubscriptionId,)import qualified Aftok.Billing as B - replacement in server/Aftok/Snaplet/Billing.hs at line 28
import Aftok.Jsonimport Aftok.Json (Version (..), badVersion, unversion)import Aftok.Payments( PaymentRequestId,PaymentsConfig,SomePaymentRequest (..),SomePaymentRequestDetail,createPaymentRequest,zcashPaymentsConfig,)import Aftok.Payments.Types( PaymentRequestError (..),)import qualified Aftok.Payments.Zcash as Zcash - replacement in server/Aftok/Snaplet/Billing.hs at line 42
import Aftok.Snaplet.Authimport Aftok.Typesimport Control.Lens ((^.))import Data.Aeson( App,readRequestJSON,requireId,requireProjectId,snapError,snapEval,)import Aftok.Snaplet.Auth (requireUserId)import Aftok.Types (ProjectId, UserId)import Control.Lens ((.~), (^.))-- import Data.Aeson () - edit in server/Aftok/Snaplet/Billing.hs at line 54
( (.:),(.:?),Object,Parser,Value (..),parseEither,parseJSON,) - replacement in server/Aftok/Snaplet/Billing.hs at line 63
import Data.Thyme.Clock as Cimport qualified Data.Thyme.Clock as C - replacement in server/Aftok/Snaplet/Billing.hs at line 65
import Snap.Snaplet as Simport Snap.Core (MonadSnap)import qualified Snap.Snaplet as S - replacement in server/Aftok/Snaplet/Billing.hs at line 113
-- subscriptionJSON :: B.Subscription -> ValuecreatePaymentRequestHandler ::MonadSnap m =>PaymentsConfig m ->S.Handler App App (PaymentRequestId, SomePaymentRequestDetail)createPaymentRequestHandler cfg = douid <- requireUserIdpid <- requireProjectIdbid <- requireId "billableId" BillableIdbillable <- snapEval $ withProjectAuth pid uid (FindBillable bid)now <- liftIO C.getCurrentTimelet billDay = now ^. C._utctDaycase billable of-- check that the billable is actually related to the project that the user-- is authorized for & the URL specifiesJust b | (b ^. B.project == pid) ->case b ^. B.amount ofAmount ZEC v -> dolet ops = Zcash.paymentOps (cfg ^. zcashPaymentsConfig)res <- snapEval . runExceptT $ createPaymentRequest ops now bid (b & B.amount .~ v) billDaycase res ofLeft AmountInvalid -> snapError 400 $ "Invalid payment amount requested."Left NoRecipients -> snapError 400 $ "This project has no payable members."Right (reqId, detail) ->pure (reqId, SomePaymentRequest detail)Amount BTC _ ->snapError 400 $ "Bitcoin payment requests not yet supported."_ ->snapError 404 $ "Billable not found."-- subscriptionJSON :: Subscription -> Value - replacement in server/Aftok/Snaplet/Billing.hs at line 145
-- subscriptionKV :: (KeyValue kv) => B.Subscription -> [kv]-- subscriptionKV :: (KeyValue kv) => Subscription -> [kv] - replacement in server/Aftok/Snaplet/Billing.hs at line 147
-- [ "user_id" .= idValue (B.customer . _UserId) sub,-- "billable_id" .= idValue (B.billable . B._BillableId) sub,-- "start_time" .= view B.startTime sub,-- "end_time" .= view B.endTime sub-- [ "user_id" .= idValue (customer . _UserId) sub,-- "billable_id" .= idValue (billable . _BillableId) sub,-- "start_time" .= view startTime sub,-- "end_time" .= view endTime sub - replacement in server/Aftok/Snaplet/Billing.hs at line 164
parseRecurrence :: Object -> Parser B.RecurrenceparseRecurrence :: Object -> Parser Recurrence - replacement in server/Aftok/Snaplet/Billing.hs at line 166
let parseAnnually o' = const (pure B.Annually) <$> O.lookup "annually" o'parseMonthly o' = fmap B.Monthly . parseJSON <$> O.lookup "monthly" o'parseWeekly o' = fmap B.Weekly . parseJSON <$> O.lookup "weekly" o'parseOneTime o' = const (pure B.OneTime) <$> O.lookup "onetime" o'let parseAnnually o' = const (pure Annually) <$> O.lookup "annually" o'parseMonthly o' = fmap Monthly . parseJSON <$> O.lookup "monthly" o'parseWeekly o' = fmap Weekly . parseJSON <$> O.lookup "weekly" o'parseOneTime o' = const (pure OneTime) <$> O.lookup "onetime" o' - replacement in server/Aftok/Snaplet/Billing.hs at line 179
parseRecurrence' :: Value -> Parser B.RecurrenceparseRecurrence' :: Value -> Parser Recurrence - replacement in server/Aftok/Snaplet/Payments.hs at line 10
import Aftok.Billingimport Aftok.Billing (SubscriptionId (..)) - replacement in shell.nix at line 23
haskellPackages.HDBC-postgresqlhaskellPackages.dbmigrations-postgresql# haskellPackages.HDBC-postgresql# haskellPackages.dbmigrations-postgresql