Payment request creation.
[?]
Feb 7, 2021, 9:40 PM
V54JCKJX4WL5UGJBCX7VR5O6QKABGUHPLYB4MD2NQQW45OFH5OBACDependencies
- [2]
H2ABVZI2Add endpoint for payment request creation. - [3]
SEWTRB6SImplement payment request creation functions. - [4]
UWMGUJOWAutoformat sources. - [5]
MU6WOCCJUpdate auctions to permit zcash as a funding currency. - [6]
X3ES7NUAFine. I'll use ormolu. At least it doesn't break the code. - [7]
M4PWY5RUPreliminary work to add support for Zcash payments. - [8]
N6FG4EW6Working bootstrap modal! Only a little FFI. - [9]
27H4DECZAdd billing create API call. - [10]
KKJSBWO6Add createPaymentRequestHandler - [11]
QMRKFEPGRefactor QDB to use a free monad algebra instead. - [12]
VTZT2ILUWire up billing navigation. - [13]
XXJFUZOVAdd first revenue date to project payout computation. - [14]
YBLHJFCNImplement billing modal. - [15]
NAFJ6RB3Minor module reorg. - [16]
U7YAT2ZKAdd error reporting to signup form. - [17]
ANDJ6GEYAdd billing component skeleton. - [18]
4GOBY5NQWIP on modals. - [19]
3PFXXJTLWIP - [20]
IR75ZMX3Return actual events for interval ends, not just timestamps. - [21]
IPG33FAWAdd billing daemon - [22]
U256ZALIAdd captcha check to register route. - [23]
T2DN23M7Factor out billing create component. - [24]
BROSTG5KBeginning of modularization of server. - [25]
3HTCTHHUAdd halogen-portal dependency and update argonaut. - [26]
KET5QGQPAdd billable list (in-progress) - [*]
64C6AWH6Rename Ananke -> Quixotic, project reboot. - [*]
GCVQD44VCreate amends endpoint, switch to UUID primary keys - [*]
GMYPBCWEMake docker-compose work. - [*]
NJNMO72SAdd zcash.com submodule and update client to modern halogen. - [*]
JXG3FCXYUpgrade ps + halogen versions. - [*]
PPW6ROC5Render project data - [*]
QH4UB73NFormat with purty. - [*]
QU5FW67RAdd project selection to time tracker. - [*]
OUR4PAOTUse local dates for display of intervals. - [*]
DFOBMSAOInitial work on payments API - [*]
2XQD6KKKAdd invitation logic and clean up DBProg error handling.
Change contents
- edit in aftok.cabal at line 181
, cryptonite - edit in client/package-lock.json at line 4340
"kjua": {"version": "0.9.0","resolved": "https://registry.npmjs.org/kjua/-/kjua-0.9.0.tgz","integrity": "sha512-Wmh5k6hpl+wiYkcEIx0/Ocj1DOxacw/myh/SQ3NbY0RWD4360CXaaAJkdeeV+moqf7fxvACYK95LXQ8vtLWKxA=="}, - edit in client/package.json at line 18
"kjua": "^0.9.0", - replacement in client/src/Aftok/Api/Billing.purs at line 6
-- import Control.Monad.Except.Trans (runExceptT, except, withExceptT)import Control.Monad.Except.Trans (runExceptT) - replacement in client/src/Aftok/Api/Billing.purs at line 13
import Data.BigInt (toNumber, fromNumber) as BigIntimport Data.BigInt (toNumber) as BigInt - replacement in client/src/Aftok/Api/Billing.purs at line 15
-- import Data.DateTime.Instant (Instant, toDateTime)import Data.DateTime.Instant (toDateTime) - replacement in client/src/Aftok/Api/Billing.purs at line 17
import Foreign.Object (Object)-- import Data.Foldable (class Foldable, foldr, foldl, foldMapDefaultR)import Data.Foldable (class Foldable, foldMapDefaultR) - replacement in client/src/Aftok/Api/Billing.purs at line 25
import Data.Traversable (traverse, sequence)import Data.UUID (UUID, parseUUID)import Data.Traversable (class Traversable, traverse, sequence)import Data.UUID (UUID, parseUUID, toString) - edit in client/src/Aftok/Api/Billing.purs at line 28
import Effect.Class (liftEffect) - replacement in client/src/Aftok/Api/Billing.purs at line 31
-- import Foreign.Object (Object)import Foreign.Object (Object) - replacement in client/src/Aftok/Api/Billing.purs at line 39
( Zatoshi(..) )( Zatoshi ) - edit in client/src/Aftok/Api/Billing.purs at line 44
, parseDatedResponse, parseZatoshi - edit in client/src/Aftok/Api/Billing.purs at line 57
billableIdStr :: BillableId -> StringbillableIdStr (BillableId uuid) = toString uuid - replacement in client/src/Aftok/Api/Billing.purs at line 139
amount <-map Zatoshi$ (note (TypeMismatch "Failed to decode as Zatoshi") <<< BigInt.fromNumber)=<< (_ .: "zatoshi")=<< (bobj .: "amount")amount <- parseZatoshi =<< (bobj .: "amount") - edit in client/src/Aftok/Api/Billing.purs at line 144
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 - replacement in client/src/Aftok/Api/Billing.purs at line 170
{{ payment_request_id :: String, native_request :: {zip321_request :: String,schemaVersion :: String}, expires_at :: t, total :: Zatoshi - edit in client/src/Aftok/Api/Billing.purs at line 178
derive instance paymentRequestFunctor :: Functor PaymentRequest' - edit in client/src/Aftok/Api/Billing.purs at line 181
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 - replacement in client/src/Aftok/Api/Billing.purs at line 195
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 responsetype PaymentRequestMeta ={ requestName :: String, requestDesc :: Maybe String} - replacement in client/src/Aftok/Api/Billing.purs at line 200
listProjectBillables :: ProjectId -> Aff (Either APIError (Array (Tuple BillableId Billable)))listProjectBillables pid = doresponse <- get RF.json ("/api/projects/" <> pidStr pid <> "/billables")parseResponse (traverse parseBillableJSON <=< decodeJson) responsedecodePaymentRequest :: 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 } - replacement in client/src/Aftok/Api/Billing.purs at line 209
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))) - edit in client/src/Aftok/Api/Billing.purs at line 227[3.2712]
- replacement in client/src/Aftok/Api/Json.purs at line 8
import Data.Argonaut.Decode (class DecodeJson, decodeJson, JsonDecodeError(..))import Data.Argonaut.Decode (class DecodeJson, decodeJson, JsonDecodeError(..), (.:))import Data.BigInt (fromNumber) as BigInt - edit in client/src/Aftok/Api/Json.purs at line 21
import Foreign.Object (Object) - edit in client/src/Aftok/Api/Json.purs at line 26
import Aftok.Zcash (Zatoshi(..)) - edit in client/src/Aftok/Api/Json.purs at line 120[3.1696]
parseZatoshi :: Object Json -> Either JsonDecodeError ZatoshiparseZatoshi obj =map Zatoshi$ (note (TypeMismatch "Failed to decode as Zatoshi") <<< BigInt.fromNumber)=<< (obj .: "zatoshi") - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 5
-- import Data.DateTime (DateTime, date)import Control.Monad.State.Class (get) - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 7
import Data.Fixed as Fixed - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 8
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.Maybe (Maybe(..))import Data.Tuple (Tuple(..)) - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 11
import Data.Time.Duration (Hours(..), Days(..))-- import Data.Traversable (traverse)import Data.Tuple (Tuple(..))import Data.Traversable (traverse_) - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 13
-- import Effect.Class (liftEffect)-- import Effect.Now (nowDateTime) - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 25
, Billable, Recurrence(..), createBillable, PaymentRequest'(..), PaymentRequest, PaymentRequestMeta, createPaymentRequest - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 30
import Aftok.Zcash (ZEC(..), toZatoshi)data Field= NameField| DescField| MessageField| MonthlyRecurrenceField| WeeklyRecurrenceField| AmountField| GracePeriodField| RequestExpiryFieldderive instance fieldEq :: Eq Fieldderive instance fieldOrd :: Ord Field - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 31
data RType= RTAnnual| RTMonthly| RTWeekly| RTOneTimedata FieldError= NameRequired| BillableIdNotSet - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 35
derive instance rtypeEq :: Eq RTypederive instance fieldEq :: Eq FieldErrorderive instance fieldOrd :: Ord FieldError - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 40
, billableId :: Maybe BillableId - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 43
, message :: Maybe String, recurrenceType :: RType, recurrenceValue :: Maybe Int, amount :: Maybe ZEC, gracePeriod :: Maybe Days, requestExpiry :: Maybe Hours, fieldErrors :: Array Field, fieldErrors :: Array FieldError - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 46
data Query a= Tell atype Input ={ projectId :: ProjectId, billableId :: Maybe BillableId} - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 51
type Input = ProjectIddata Query a =SetBillableId BillableId a - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 54
type Output = Tuple BillableId Billabletype Output = PaymentRequest - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 57
= ProjectChanged ProjectId| SetName String= SetName String - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 59
| SetMessage String| SetRecurrenceType RType| SetRecurrenceMonths String| SetRecurrenceWeeks String| SetBillingAmount String| SetGracePeriod String| SetRequestExpiry String| SaveBillable| SavePaymentRequest - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 65
= { createBillable :: ProjectId -> Billable -> m (Either APIError BillableId)= { createPaymentRequest ::ProjectId ->BillableId ->PaymentRequestMeta ->m (Either APIError PaymentRequest) - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 72
modalId :: StringmodalId = "createPaymentRequest" - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 88
{ handleAction = eval, receive = Just <<< ProjectChanged{ handleAction = handleAction, handleQuery = handleQuery - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 95
{ projectId: input{ projectId: input.projectId, billableId: input.billableId - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 99
, message : Nothing, recurrenceType : RTOneTime, recurrenceValue : Nothing, amount : Nothing, gracePeriod : Nothing, requestExpiry : Nothing - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 104
Modals.modalWithSave "createBillable" "Create Billable" SaveBillableModals.modalWithSave modalId "Create Payment Request" SavePaymentRequest - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 107
[ NameField ][ HH.label[ P.for "billableName"][ HH.text "Product Name" ][ NameRequired ][ HH.label[ P.for "requestName"][ HH.text "Request Name" ] - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 113
, P.classes [ C.formControlSm ], P.id_ "billableName", P.placeholder "A name for the product or service you want to bill for", P.classes [ C.formControl, C.formControlSm ], P.id_ "requestName", P.placeholder "A name for the payment request" - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 118
], 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)] - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 120
[ MessageField ][ HH.label[ P.for "billableMsg"][ HH.text "Message to be included with bill" ][ ][ HH.label[ P.for "requestDesc"][ HH.text "Request Description" ] - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 126
, 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) - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 131
, HH.div[ P.classes [ ClassName "input-group-append" ] ][ HH.span [ P.classes [ ClassName "input-group-text" ] ] [ HH.text "ZEC" ] ] - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 132
, 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)]] - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 135
formGroup :: forall i a. CState -> Array Field -> Array (HH.HTML i a) -> HH.HTML i aformGroup st fields body =formGroup :: forall i a. CState -> Array FieldError -> Array (HH.HTML i a) -> HH.HTML i aformGroup st fields body = - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 139
(body <> (fieldError st =<< fields))(body <> (fieldError st =<< fields)) - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 141
fieldError :: forall i a. CState -> Field -> Array (HH.HTML i a)fieldError st field =if any (_ == field) st.fieldErrorsfieldError :: forall i a. CState -> FieldError -> Array (HH.HTML i a)fieldError st field =if any (_ == field) st.fieldErrors - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 145
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." - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 151
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<*> expiryVhandleQuery :: 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) - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 157
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) - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 185
{ createBillable: createBillable{ createPaymentRequest: createPaymentRequest - replacement in client/src/Aftok/Billing/PaymentRequest.purs at line 190
{ 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 - edit in client/src/Aftok/Billing/PaymentRequest.purs at line 247[3.14505]
renderQR :: PaymentRequest -> m UnitrenderQR (PaymentRequest r) =system.renderQR "paymentRequestQRCode" { content: r.native_request.zip321_request } - edit in client/src/Aftok/Billing.purs at line 15
import DOM.HTML.Indexed.ButtonType (ButtonType(..)) - replacement in client/src/Aftok/Billing.purs at line 22
-- import Halogen.HTML.Events as Eimport Halogen.HTML.Events as E - edit in client/src/Aftok/Billing.purs at line 39
import Aftok.HTML.Classes as C - edit in client/src/Aftok/Billing.purs at line 41
import Aftok.Modals.ModalFFI as ModalFFI - replacement in client/src/Aftok/Billing.purs at line 50
, selectedBillable :: Maybe (Tuple BillableId Billable), selectedBillable :: Maybe BillableId - edit in client/src/Aftok/Billing.purs at line 58
| CreatePaymentRequest BillableId| PaymentRequestCreated (PaymentRequest) - edit in client/src/Aftok/Billing.purs at line 68
, showPaymentRequest :: PaymentRequest.QrSlot Unit - edit in client/src/Aftok/Billing.purs at line 74
_showPaymentRequest = SProxy :: SProxy "showPaymentRequest" - edit in client/src/Aftok/Billing.purs at line 78
, createPaymentRequest :: PaymentRequest.Capability m - replacement in client/src/Aftok/Billing.purs at line 137
[ Modals.modalButton "createBillable" "Create billable" ][ Modals.modalButton "createBillable" "Create billable" Nothing] - replacement in client/src/Aftok/Billing.purs at line 146
_createBillable_createPaymentRequest - replacement in client/src/Aftok/Billing.purs at line 148
(Create.component system caps.createBillable)(unwrap p).projectId(PaymentRequest.component system caps.createPaymentRequest){ projectId: (unwrap p).projectId, billableId: st.selectedBillable } - replacement in client/src/Aftok/Billing.purs at line 151
(Just <<< BillableCreated)(Just <<< PaymentRequestCreated), system.portal_showPaymentRequestunit(PaymentRequest.qrcomponent system)NothingNothing(const Nothing) - edit in client/src/Aftok/Billing.purs at line 182
- replacement in client/src/Aftok/Billing.purs at line 192
[ Modals.modalButton "createPaymentRequest" "New Payment Request" ][ HH.button[ P.classes [ C.btn, C.btnPrimary ], P.type_ ButtonButton, E.onClick (\_ -> Just $ CreatePaymentRequest bid)][ HH.text "New Payment Request" ]] - edit in client/src/Aftok/Billing.purs at line 203
- edit in client/src/Aftok/Billing.purs at line 222
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 - edit in client/src/Aftok/Billing.purs at line 246
, createPaymentRequest: PaymentRequest.apiCapability - replacement in client/src/Aftok/Billing.purs at line 253
{ createBillable: { createBillable: \_ _ -> pure $ Left Forbidden }{ createBillable: Create.mockCapability, createPaymentRequest: PaymentRequest.mockCapability - file addition: KjuaQR.js[3.3158]
exports.renderQRInternal = selector => content => () => {$('#' + selector).kjua(content)} - file addition: KjuaQR.purs[3.3158]
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 - replacement in client/src/Aftok/Modals.purs at line 15
modalButton :: forall w i. String -> String -> HH.HTML w imodalButton target text =modalButton :: forall action slots m. String -> String -> Maybe action -> H.ComponentHTML action slots mmodalButton target text action = - edit in client/src/Aftok/Modals.purs at line 22
, E.onClick (\_ -> action) - replacement in client/src/Aftok/Modals.purs at line 26
modalWithSave ::forall action slots m.String ->String ->modalWithSave ::forall action slots m.String ->String -> - replacement in client/src/Aftok/Modals.purs at line 31
Array (H.ComponentHTML action slots m) ->Array (H.ComponentHTML action slots m) -> - edit in client/src/Aftok/Modals.purs at line 78[3.6393]
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" ]]]]] - edit in client/src/Aftok/Types.purs at line 29
import Aftok.HTML.KjuaQR as KjuaQR - edit in client/src/Aftok/Types.purs at line 54
, renderQR :: String -> KjuaQR.QROpts -> m Unit - edit in client/src/Aftok/Types.purs at line 69
, renderQR: \i opts -> liftEffect (KjuaQR.renderQR i opts) - edit in lib/Aftok/Config.hs at line 10
import Aftok.Currency.Zcash.Types (Memo(..)) - edit in lib/Aftok/Config.hs at line 15
import Aftok.Types (AccountId) - edit in lib/Aftok/Config.hs at line 21
)import Crypto.Random.Types( MonadRandom,getRandomBytes, - edit in lib/Aftok/Config.hs at line 41
import Haskoin.Address (encodeBase58Check) - replacement in lib/Aftok/Config.hs at line 137
toPaymentsConfig :: MonadDB m => BillingConfig -> IO (PaymentsConfig m)toPaymentsConfig :: (MonadRandom m, MonadDB m) => BillingConfig -> IO (PaymentsConfig m) - replacement in lib/Aftok/Config.hs at line 140
let btcOps = Bitcoin.BillingOps _memoGen (_uriGen $ cfg ^. bitcoinConfig . bip70Host) _payloadGenlet btcOps = Bitcoin.BillingOps _btcMemoGen (_uriGen $ cfg ^. bitcoinConfig . bip70Host) _payloadGen - edit in lib/Aftok/Config.hs at line 144
_zcashBillingOps = _zcashMemoGen, - replacement in lib/Aftok/Config.hs at line 148
_memoGen ::_btcMemoGen :: - replacement in lib/Aftok/Config.hs at line 154
_memoGen bill billingDate requestTime = do_btcMemoGen bill billingDate requestTime = do - edit in lib/Aftok/Config.hs at line 168
_zcashMemoGen ::(MonadRandom m, MonadDB m) =>B.Billable Zatoshi ->Day ->UTCTime ->AccountId ->m (Maybe Memo)_zcashMemoGen _ _ _ _ = dopkey <- encodeBase58Check <$> getRandomBytes 32-- for nowpure $ Just (Memo $ encodeUtf8 pkey) - edit in lib/Aftok/Config.hs at line 200[2.4481]
- replacement in lib/Aftok/Currency/Zcash/Zip321.hs at line 63
paramIndex = maybe "" (\i -> pack (printf ".%d" i)) . find (> 0)paramIndex = \caseJust i | i > 0 -> pack (printf ".%d" i)_ -> "" - replacement in lib/Aftok/Currency/Zcash/Zip321.hs at line 100
intercalate "&" . toList . itemParams <$> zip (Just <$> fromList [1 ..]) xsintercalate "&" . toList . itemParams <$> zip (Just <$> fromList [0 ..]) xs - replacement in lib/Aftok/Database/PostgreSQL/Users.hs at line 87
findUserPaymentAddress :: UserId -> Currency a c -> DBM (Maybe a)findUserPaymentAddress :: UserId -> Currency a c -> DBM (Maybe (AccountId, a)) - replacement in lib/Aftok/Database/PostgreSQL/Users.hs at line 93
(bitcoinAddressParser mode)[sql| SELECT btc_addr FROM cryptocurrency_accounts((,) <$> idParser AccountId <*> bitcoinAddressParser mode)[sql| SELECT id, btc_addr FROM cryptocurrency_accounts - replacement in lib/Aftok/Database/PostgreSQL/Users.hs at line 102
(zcashAddressParser)[sql| SELECT zcash_addr FROM cryptocurrency_accounts((,) <$> idParser AccountId <*> zcashAddressParser)[sql| SELECT id, zcash_addr FROM cryptocurrency_accounts - replacement in lib/Aftok/Database.hs at line 79
FindUserPaymentAddress :: UserId -> Currency a c -> DBOp (Maybe a)FindUserPaymentAddress :: UserId -> Currency a c -> DBOp (Maybe (AccountId, a)) - replacement in lib/Aftok/Database.hs at line 170
findUserPaymentAddress :: (MonadDB m) => UserId -> Currency a c -> MaybeT m afindUserPaymentAddress :: (MonadDB m) => UserId -> Currency a c -> MaybeT m (AccountId, a) - replacement in lib/Aftok/Database.hs at line 173
findAccountPaymentAddress :: (MonadDB m) => AccountId -> Currency a c -> MaybeT m afindAccountPaymentAddress uid n = MaybeT . liftdb $ FindAccountPaymentAddress uid nfindAccountPaymentAddress :: (MonadDB m) => AccountId -> Currency a c -> MaybeT m (AccountId, a)findAccountPaymentAddress aid n = fmap (aid,) . MaybeT . liftdb $ FindAccountPaymentAddress aid n - edit in lib/Aftok/Payments/Bitcoin.hs at line 27
import Aftok.Types (AccountId) - replacement in lib/Aftok/Payments/Bitcoin.hs at line 156
toOutput :: (Address, Satoshi) -> Either PaymentError OutputtoOutput (addr, amt) = case addr oftoOutput :: ((AccountId, Address), Satoshi) -> Either PaymentError OutputtoOutput ((_, addr), amt) = case addr of - replacement in lib/Aftok/Payments/Util.hs at line 20
import Aftok.Types (ProjectId)import Aftok.Types (ProjectId, AccountId) - replacement in lib/Aftok/Payments/Util.hs at line 53
ExceptT PaymentRequestError m (Map a c)ExceptT PaymentRequestError m (Map (AccountId, a) c) - replacement in lib/Aftok/Payments/Util.hs at line 76
ExceptT PaymentRequestError m [(a, c)]ExceptT PaymentRequestError m [((AccountId, a), c)] - edit in lib/Aftok/Payments/Zcash.hs at line 13
import Aftok.Currency.Zcash.Types (Memo(..)) - edit in lib/Aftok/Payments/Zcash.hs at line 18
import Aftok.Types (AccountId) - edit in lib/Aftok/Payments/Zcash.hs at line 32
type MemoGen m = Billable Zatoshi -> C.Day -> C.UTCTime -> AccountId -> m (Maybe Memo) - edit in lib/Aftok/Payments/Zcash.hs at line 36
MemoGen m -> - replacement in lib/Aftok/Payments/Zcash.hs at line 39
paymentOps cfg =paymentOps memoGen cfg = - replacement in lib/Aftok/Payments/Zcash.hs at line 41
{ PT.newPaymentRequest = ((fmap PT.Zip321Request .) .) . zip321PaymentRequest cfg{ PT.newPaymentRequest = ((fmap PT.Zip321Request .) .) . zip321PaymentRequest cfg memoGen - edit in lib/Aftok/Payments/Zcash.hs at line 44
-- 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. - edit in lib/Aftok/Payments/Zcash.hs at line 52
forall m. - edit in lib/Aftok/Payments/Zcash.hs at line 55
-- | generator for the memo to be associated with each itemMemoGen m -> - replacement in lib/Aftok/Payments/Zcash.hs at line 64
zip321PaymentRequest cfg billable billingDay _ = dozip321PaymentRequest cfg memoGen billable billingDay billTime = do - replacement in lib/Aftok/Payments/Zcash.hs at line 69
PaymentRequest <$> (tryJust PT.NoRecipients $ nonEmpty (toPaymentItem <$> assocs payouts))itemsMay <- lift $ nonEmpty <$> traverse toPaymentItem (assocs payouts)PaymentRequest <$> tryJust PT.NoRecipients itemsMay - replacement in lib/Aftok/Payments/Zcash.hs at line 72
toPaymentItem :: (Address, Zatoshi) -> PaymentItemtoPaymentItem (a, z) =PaymentItemtoPaymentItem :: ((AccountId, Address), Zatoshi) -> m PaymentItemtoPaymentItem ((aid, a), z) = domemo <- memoGen billable billingDay billTime aidpure $ PaymentItem - replacement in lib/Aftok/Payments/Zcash.hs at line 80
_memo = Nothing, -- Just . Memo $ toASCIIBytes (reqid ^. PT._PaymentRequestId),_memo = memo, - edit in lib/Aftok/Payments.hs at line 79
_zcashBillingOps :: !(Zcash.MemoGen m), - replacement in lib/Aftok/Payments.hs at line 136
let ops = Zcash.paymentOps (cfg ^. zcashPaymentsConfig)let ops = Zcash.paymentOps (cfg ^. zcashBillingOps) (cfg ^. zcashPaymentsConfig) - edit in server/Aftok/Snaplet/Billing.hs at line 26
import Aftok.Database.PostgreSQL (QDBM) - edit in server/Aftok/Snaplet/Billing.hs at line 29
MonadDB, - edit in server/Aftok/Snaplet/Billing.hs at line 50
zcashBillingOps - edit in server/Aftok/Snaplet/Billing.hs at line 68
qdbmEval - edit in server/Aftok/Snaplet/Billing.hs at line 73
import Control.Monad.Trans.Except (mapExceptT) - replacement in server/Aftok/Snaplet/Billing.hs at line 132
MonadDB m =>PaymentsConfig m ->PaymentsConfig QDBM -> - replacement in server/Aftok/Snaplet/Billing.hs at line 147
let ops = Zcash.paymentOps (cfg ^. zcashPaymentsConfig)res <- snapEval . runExceptT $ createPaymentRequest ops now bid (b & B.amount .~ v) billDaylet ops = Zcash.paymentOps (cfg ^. zcashBillingOps) (cfg ^. zcashPaymentsConfig)res <- runExceptT . mapExceptT qdbmEval $ createPaymentRequest ops now bid (b & B.amount .~ v) billDay - replacement in server/Aftok/Snaplet/Billing.hs at line 175
obj $ ["payment_request_id" .= (rid ^. _PaymentRequestId)] <> fields reqv1 . obj $["payment_request_id" .= (rid ^. _PaymentRequestId)] <> fields req - replacement in server/Aftok/Snaplet.hs at line 13
import Aftok.Database.PostgreSQL (runQDBM)import Aftok.Database.PostgreSQL (QDBM, runQDBM) - edit in server/Aftok/Snaplet.hs at line 84[3.74512][38.11945]
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)