V54JCKJX4WL5UGJBCX7VR5O6QKABGUHPLYB4MD2NQQW45OFH5OBAC H2ABVZI2NFTERQMJ2Z7WGMRNORV3OQQWCCFEN6YO5GAUT2ONM2MAC GCVQD44VRPQVKPZEPIC4AOIXLJIG2ZMV3QI2Y7KALUT6NVUBSGSAC GMYPBCWEB6NKURRILAHR3TJUKDOGR2ZMK5I6MS6P5G2LAGH36P3QC 64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC NJNMO72S7VIUV22JXB4IFPZMHWTJAOTP6EC6Z4QSKYIROSXT52MQC PPW6ROC5U7FZCJCH2RX7UJ3PJYNPUMNEZ6KKO3375VFRUM4VT3VQC QH4UB73NUR2XPHZQ2RGJBKKUBN43RKC7ZJBCFPP4ESUIIEDDR5XQC JXG3FCXYBDKMUD77DOM7RCIJYKB7BILC43OHHDZBE7YQRGAMUCCAC KET5QGQPM5STWGRDL72HTZ5T57QRKQQ3L564PST2PNG4YJHKATSAC ANDJ6GEY2IRDNKPVXESYEZKU24BAXFB5PPSZFIJRMBGL57A622FQC 27H4DECZW4CEDSV5XYJQA5HOMUW73K5G2DBQNLQB7AFZXXVXCFCAC KKJSBWO6RNORAPTJPCCUJJNVI2OYTGLQKB3XJGOASH43GNTJBMKAC VTZT2ILU7VWP5EY4526HU72Z5HZB6VRVQIVJJTB6Q5NL2AUFZRSAC 3PFXXJTLLGDWIFVI32VDUSVGGQL73F6KBACLD2GGJO2AAIS4VPJAC NAFJ6RB3KYDBSTSNB3WQSVUQEPUGG2RZCBWRF4XNT2UKSOXDNMDQC 3HTCTHHULQUAHAQFUKDIFO3S7FVVFXMAQLLS3T44MLHGDIT5DZGAC YBLHJFCNW52TJ37UIHPZ6UD22SQVGG27SP5UQR7YAIJ7F7SYJZSAC 4GOBY5NQYPISPYKVN74SM7JYWV7PALUDWWGVXWRHW2J2CPPMC42QC T2DN23M7W53UMRV46SKDP6UDMCZB7VG2J772LXKMAJNL6NA62MKAC N6FG4EW6QU7V6QV7UHHYRA3EDKPGVCAEAT7IS3QI45N3GRRV2V7AC QU5FW67RGCWOWT2YFM4NYMJFFHWIRPQANQBBAHBKZUY7UYMCSIMQC OUR4PAOTXXKXQPMAR5TIYX7MBRRJS2WVTZS7SN4SOGML7SPJIJGQC IPG33FAWXGEQ2PO6OXRT2PWWXHRNMPVUKKADL6UKKN5GD2CNZ25AC X3ES7NUA42D2BF7CQDDKXM5CLMVCYA3H5YU5KXLPTGDBFPE2LNVAC M4PWY5RUV72AEDCNC4O7UKBPHBIACR4354YTSC3SUZGWFV5UBJBQC U256ZALIPTVWLNACYPIMWLNEYDQWP7CHF4Y4CGMILQTONJHMGQVQC QMRKFEPGFBCEWAIXPEIG5ILKAJ2JH5L3TOITHR4HNJXK5FN3KXBQC U7YAT2ZK6GMS7KVFFEQTDRFX6GIN7HVHNWGKIFDGJGE2G2IXSF6QC DFOBMSAODB3NKW37B272ZXA2ML5HMIH3N3C4GT2DPEQS7ZFK4SNAC BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC 2XQD6KKKD6QVHFHAEMVE3XXY7X2T7BLTLL7XIILZAXNJJH2YONUQC import Data.Traversable (traverse, sequence)import Data.UUID (UUID, parseUUID)
import Data.Traversable (class Traversable, traverse, sequence)import Data.UUID (UUID, parseUUID, toString)
createBillable :: ProjectId -> Billable -> Aff (Either APIError BillableId)createBillable pid billable = dolet body = RB.json $ billableJSON billableresponse <- post RF.json ("/api/projects/" <> pidStr pid <> "/billables") (Just body)parseResponse decodeJson responselistProjectBillables :: ProjectId -> Aff (Either APIError (Array (Tuple BillableId Billable)))listProjectBillables pid = doresponse <- get RF.json ("/api/projects/" <> pidStr pid <> "/billables")parseResponse (traverse parseBillableJSON <=< decodeJson) response
instance paymentRequestFoldable :: Foldable PaymentRequest' wherefoldr f b (PaymentRequest r) =f r.expires_at bfoldl f b (PaymentRequest r) =f b r.expires_atfoldMap = foldMapDefaultRinstance paymentRequestTraversable :: Traversable PaymentRequest' wheretraverse f (PaymentRequest r) =map (\b -> PaymentRequest (r { expires_at = b })) (f r.expires_at)sequence = traverse identity
createBillable :: ProjectId -> Billable -> Aff (Either APIError BillableId)createBillable pid billable = dolet body = RB.json $ billableJSON billableresponse <- post RF.json ("/api/projects/" <> pidStr pid <> "/billables") (Just body)parseResponse decodeJson response
type PaymentRequestMeta ={ requestName :: String, requestDesc :: Maybe String}
listProjectBillables :: ProjectId -> Aff (Either APIError (Array (Tuple BillableId Billable)))listProjectBillables pid = doresponse <- get RF.json ("/api/projects/" <> pidStr pid <> "/billables")parseResponse (traverse parseBillableJSON <=< decodeJson) response
decodePaymentRequest :: Json -> Either JsonDecodeError (PaymentRequest' String)decodePaymentRequest json = doobj <- decodeJson jsonpayment_request_id <- obj .: "payment_request_id"native_request <- obj .: "native_request"expires_at <- obj .: "expires_at"total <- parseZatoshi =<< (obj .: "total")pure $ PaymentRequest { payment_request_id, native_request, expires_at, total }
listUnpaidPaymentRequests :: BillableId -> Aff (Either APIError (Array (Tuple PaymentRequestId PaymentRequest)))
createPaymentRequest ::ProjectId ->BillableId ->PaymentRequestMeta ->Aff (Either APIError PaymentRequest)createPaymentRequest pid bid m = dolet body = RB.json (encodeJson m)uri = "/api/projects/" <> pidStr pid <> "/billables/" <> billableIdStr bid <> "/paymentRequests"response <- post RF.json uri (Just body)liftEffect<<< runExceptT<<< map (map toDateTime)$ parseDatedResponse decodePaymentRequest responselistUnpaidPaymentRequests ::BillableId ->Aff (Either APIError (Array (Tuple PaymentRequestId PaymentRequest)))
parseZatoshi :: Object Json -> Either JsonDecodeError ZatoshiparseZatoshi obj =map Zatoshi$ (note (TypeMismatch "Failed to decode as Zatoshi") <<< BigInt.fromNumber)=<< (obj .: "zatoshi")
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 U
import Data.Maybe (Maybe(..))import Data.Tuple (Tuple(..))
], 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)]
, 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)
, P.classes [ C.formControl, C.formControlSm ], P.id_ "requestDesc", P.placeholder "Additional descriptive information", E.onValueInput (Just <<< SetDesc)
, 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)]]
NameField -> 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."
NameRequired -> err "The name field is required"BillableIdNotSet -> err "The billable id is missing. Close this dialog and try again."
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<*> expiryV
handleQuery :: forall slots a. Query a -> H.HalogenM CState Action slots Output m (Maybe a)handleQuery = case _ ofSetBillableId bid a -> doH.modify_ (_ { billableId = Just bid })pure (Just a)
case 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)
handleAction :: forall slots. Action -> H.HalogenM CState Action slots Output m UnithandleAction = case _ ofSetName name ->H.modify_ (_ { name = Just name })SetDesc desc ->H.modify_ (_ { description = Just desc })SavePaymentRequest -> dobidV <- V <<< note [BillableIdNotSet] <$> H.gets (_.billableId)nameV <- V <<< note [NameRequired] <$> H.gets (_.name)descV <- H.gets (_.description)let reqV = { requestName: _, requestDesc: _ } <$> nameV <*> pure descVbreqV = Tuple <$> bidV <*> reqVcase toEither breqV ofLeft errors -> doH.modify_ (_ { fieldErrors = errors })Right (Tuple bid reqMeta) -> dopid <- H.gets (_.projectId)res <- lift $ caps.createPaymentRequest pid bid reqMetacase res ofRight content -> dolift $ system.log "Request created."H.raise contentlift $ system.toggleModal "createPaymentRequest" ModalFFI.HideModalLeft errs ->lift $ system.error (show errs)
{ createBillable: \_ _ -> pure $ Left Forbidden }
{ createPaymentRequest: \_ _ _ -> pure $ Left Forbidden }type QrState = Maybe PaymentRequestdata QrQuery a= QrRender PaymentRequest adata QrAction= QrInittype QrSlot id= H.Slot QrQuery Unit idqrModalId :: StringqrModalId = "paymentRequestQR"qrcomponent ::forall m output.Monad m =>System m ->H.Component HH.HTML QrQuery (Maybe PaymentRequest) output mqrcomponent system =H.mkComponent{ initialState, render, eval:H.mkEval$ H.defaultEval{ handleAction = handleAction, handleQuery = handleQuery, initialize = Just QrInit}}whereinitialState :: Maybe PaymentRequest -> Maybe PaymentRequestinitialState input = inputrender :: forall slots. QrState -> H.ComponentHTML QrAction slots mrender st =Modals.modalWithClose qrModalId "Payment Request"[ HH.div_[ HH.div [P.id_ "paymentRequestQRCode"] []]]handleQuery :: forall slots a. QrQuery a -> H.HalogenM QrState QrAction slots output m (Maybe a)handleQuery = case _ ofQrRender r a -> dolift $ renderQR rpure (Just a)handleAction :: forall slots. QrAction -> H.HalogenM QrState QrAction slots output m UnithandleAction = case _ ofQrInit -> dotraverse_ (lift <<< renderQR) =<< get
renderQR :: PaymentRequest -> m UnitrenderQR (PaymentRequest r) =system.renderQR "paymentRequestQRCode" { content: r.native_request.zip321_request }
CreatePaymentRequest bid -> doH.modify_ (_ { selectedBillable = Just bid })_ <- H.query _createPaymentRequest unit $ H.tell (PaymentRequest.SetBillableId bid)lift $ system.toggleModal PaymentRequest.modalId ModalFFI.ShowModalPaymentRequestCreated req -> dolift $ system.log "Created payment request, closing modal."lift $ system.toggleModal PaymentRequest.modalId ModalFFI.HideModallift $ system.log "About to show QR code modal"lift $ system.toggleModal PaymentRequest.qrModalId ModalFFI.ShowModal_ <- H.query _showPaymentRequest unit $ H.tell (PaymentRequest.QrRender req)pure unit
exports.renderQRInternal = selector => content => () => {$('#' + selector).kjua(content)}
module Aftok.HTML.KjuaQR( QRType(..), QROpts, renderQR)whereimport Preludeimport Effect (Effect)data QRType= Canvas| Image| SVGdata ErrorCorrection = L | M | Q | HrenderQR :: String -> QROpts -> Effect UnitrenderQR = renderQRInternaltype QROpts ={ content :: String}-- -- render method: 'canvas', 'image' or 'svg'-- render :: QRType,-- -- render pixel-perfect lines-- crisp :: Boolean,-- -- minimum version: 1..40-- minVersion :: Int,-- -- error correction level: 'L', 'M', 'Q' or 'H'-- ecLevel :: ErrorCorrection,-- -- size in pixel: 200-- size :: Int,-- -- pixel-ratio, null for devicePixelRatio-- -- ratio :: null,---- --code color: '#333',-- fill :: String -- hack, fine for now-- -- background color '#fff'-- back :: String,---- -- content-- text :: String,---- -- roundend corners in pc: 0..100-- rounded: Int,---- -- quiet zone in modules: 0-- quiet: Int,---- -- modes: 'plain', 'label' or 'image'-- mode: 'plain',---- -- label/image size and pos in pc: 0..100-- mSize: 30,-- mPosX: 50,-- mPosY: 50,---- -- label-- label: 'no label',-- fontname: 'sans',-- fontcolor: '#333',---- -- image element-- image: null---- type QROptsInternal =-- {-- -- render method: 'canvas', 'image' or 'svg'-- render :: String,---- -- render pixel-perfect lines-- crisp :: Boolean,---- -- minimum version: 1..40-- minVersion :: Int---- -- error correction level: 'L', 'M', 'Q' or 'H'-- ecLevel: 'L',---- -- size in pixel-- size: 200,---- -- pixel-ratio, null for devicePixelRatio-- ratio: null,---- -- code color-- fill: '#333',---- -- background color-- back: '#fff',---- -- content-- text: 'no text',---- -- roundend corners in pc: 0..100-- rounded: 0,---- -- quiet zone in modules-- quiet: 0,---- -- modes: 'plain', 'label' or 'image'-- mode: 'plain',---- -- label/image size and pos in pc: 0..100-- mSize: 30,-- mPosX: 50,-- mPosY: 50,---- -- label-- label: 'no label',-- fontname: 'sans',-- fontcolor: '#333',---- -- image element-- image: null------ }foreign import renderQRInternal :: String -> QROpts -> Effect Unit
modalWithClose ::forall i w.String ->String ->Array (HH.HTML i w) ->HH.HTML i wmodalWithClose modalId title contents =HH.div[ P.classes [ C.modal ], P.id_ modalId, P.tabIndex (negate 1), ARIA.role "dialog", ARIA.labelledBy (modalId <> "Title"), ARIA.hidden "true"][ HH.div[ P.classes [C.modalDialog], ARIA.role "document" ][ HH.div[ P.classes [C.modalContent] ][ HH.div[ P.classes [C.modalHeader] ][ HH.h5 [P.classes [C.modalTitle], P.id_ (modalId <>"Title") ] [HH.text title], HH.button[ P.classes [ C.close ], AP.dataDismiss "modal", ARIA.label "Close", P.type_ ButtonButton][ HH.span [ARIA.hidden "true"] [HH.text "×"]]], HH.div[ P.classes [C.modalBody] ]contents, HH.div[ P.classes [C.modalFooter] ][ HH.button[ P.type_ ButtonButton, P.classes [ C.btn, C.btnSecondary], AP.dataDismiss "modal"][ HH.text "Close" ]]]]]
findAccountPaymentAddress :: (MonadDB m) => AccountId -> Currency a c -> MaybeT m afindAccountPaymentAddress uid n = MaybeT . liftdb $ FindAccountPaymentAddress uid n
findAccountPaymentAddress :: (MonadDB m) => AccountId -> Currency a c -> MaybeT m (AccountId, a)findAccountPaymentAddress aid n = fmap (aid,) . MaybeT . liftdb $ FindAccountPaymentAddress aid n
-- TODO: Return a richer type that can include per-item uniqueness that can-- be used for tracking payments. A payment request, though it's a request for-- a single transaction, is really a request for multiple payments that we need-- to be able to verify individually since they'll be independent notes.---- However, this doesn't really become important until we start generating addresses-- from Zcash IVKs, so it's not essential for right now.
toPaymentItem :: (Address, Zatoshi) -> PaymentItemtoPaymentItem (a, z) =PaymentItem
toPaymentItem :: ((AccountId, Address), Zatoshi) -> m PaymentItemtoPaymentItem ((aid, a), z) = domemo <- memoGen billable billingDay billTime aidpure $ PaymentItem
let ops = Zcash.paymentOps (cfg ^. zcashPaymentsConfig)res <- snapEval . runExceptT $ createPaymentRequest ops now bid (b & B.amount .~ v) billDay
let ops = Zcash.paymentOps (cfg ^. zcashBillingOps) (cfg ^. zcashPaymentsConfig)res <- runExceptT . mapExceptT qdbmEval $ createPaymentRequest ops now bid (b & B.amount .~ v) billDay
either handleDBError pure eqdbmEval ::(MonadSnap m, HasPostgres m, HasNetworkMode m) => QDBM a -> m aqdbmEval p = dolet handleDBError (OpForbidden (UserId uid) reason) =snapError 403 $ show reason <> " (User " <> show uid <> ")"handleDBError (SubjectNotFound) =snapError404"The subject of the requested operation could not be found."handleDBError (EventStorageFailed) =snapError 500 "The event submitted could not be saved to the log."nmode <- getNetworkModee <- liftPG $\conn -> liftIO $ runExceptT (runQDBM nmode conn p)