Adds data structures for payments system. Adds a billing-related table creation migration.
DFOBMSAODB3NKW37B272ZXA2ML5HMIH3N3C4GT2DPEQS7ZFK4SNAC
NAS4BFL43SIUAAC663R6VFBHQ2DKI45K6Y6ZKVQI7S5547HBAN7QC
AXKKXBWN4EMUOLV43WN52JSKJPBV7TLSGLNJW5EZXHSJNKCYUWOQC
ADMKQQGCGVSHHIMVQ4XFRDCG544SBJCYALSKZV45CQQBZ4ACUH2AC
POX3UAMTGCNS3SU5I6IKDNCCSHEAUSFBZ3WCMQ3EXKPNXETOI6BAC
EZQG2APB36DDMIAYDPPDGOIXOD7K2RZZSGC2NKGZIHB2HZBTW7EQC
BROSTG5KP3NUNLSYPVQID254TE47E5RKQAKLPIY7PGWETE6JNMTAC
4U7F3CPIDTK6JSEDMNMHVKSR7HOQDLZQD2PPVMDLHO5SFSIMUXZAC
64C6AWH66FDKU6UE6Z6JPX2J2GBM2JOPTH2GL6LHKAIUBGNGDZ5AC
HALRDT2F22DAQ54M334YSKD2XJCAXDXEZPDKGMOWKH7XELBAS6MAC
2XQD6KKKD6QVHFHAEMVE3XXY7X2T7BLTLL7XIILZAXNJJH2YONUQC
KEP5WUFJXTMKRRNZLYTGYYWA4VLFCMHTKTJYF5EA5IWBYFMU6WYQC
ZP62WC472OTQETO2HTHIQIPO57XZIWVKPA4KL62GYU4OZDMB6NSAC
WO2MINIF4TXOHWSE7JWXRZYN64XRVLYIRFMF4SMPSOXKA2V77KMQC
WZUHEZSBRKHQMNWDKVG4X6DDIQEAXTGI6IGAJ5ERPRQ3W2KUMX4QC
NEDDHXUK3GNFMOFO3KLU7NRIHCTYNWBT3D6HTKZAOXFDE6HMDZ6AC
IZEVQF627FA7VV25KJAWYWGGC35LZUUBBQRPN5ZAINTQLPEDRTEAC
RN7EI6INGUUHGMNY5RU3NH56WPLRZY5ZMYDNFMNE3TGV4ESFLQIAC
GCVQD44VRPQVKPZEPIC4AOIXLJIG2ZMV3QI2Y7KALUT6NVUBSGSAC
3QVT6MA6I2CILQH3LUZABS4YQ7MN6CNRYTDRVS376OOHTPLYTFJAC
QMRKFEPGFBCEWAIXPEIG5ILKAJ2JH5L3TOITHR4HNJXK5FN3KXBQC
TNR3TEHKVADAEZSTOD2XLSUTSW5AWST2YUW4CWK5KE7DSC6XHZNAC
O5FVTOM6YFBLEPF3S576K6IMT6ZZ5VQCSB3YVXNS4CKBITKCRZ7AC
NLZ3JXLOOIL37O3RRQWXHNPNSNEOOLPD6MCB754BEBECQB3KGR2AC
ASF3UPJLCX7KIUCNJD5KAXSPDXCUEJHLL4HBRTRUBPCW73IXOCWQC
BWN72T44GRRZ6K2OPN56FTLNEB7J7AGC7T2U5HSMLEKUPGJP2NUAC
W35DDBFYF6Z4ZPCFEO5RPAONZLYCSNTXUSTS6FIUUVZHCI6Q7GHAC
Z3MK2PJ5U222DXRS22WCDHVPZ7HVAR3HOCUNXIGX6VMEPBQDF6PQC
7VGYLTMURLVSVUYFW7TCRZTDQ6RE2EPSPPA43XKHDOBFWYVVSJHQC
NVOCQVASZWTKQJG7GPH7KHKZZR7NUG4WLV5YY4KAIRPCJRWCZPIAC
75N3UJ4JK56KXF56GASGPAWLFYGJDETVJNYTF4KXFCQM767JUU5AC
Z7KS5XHHC6PAMTVBHXY7KUSS3BWAOU6FSYIITUCFOOJZU4OUJHBAC
2WOOGXDHVQ6L2MQYUTLJ6H6FVSQNJN6SMJL5DG7HAHFYPJLRT2SAC
BXGLKYRXO2O4NRM3BLNWQ7AWVPQXAMFS57MFYHJNOZZEZZW5BH6AC
{-# LANGUAGE TemplateHaskell #-}
module Aftok.Billables where
import ClassyPrelude
import Control.Lens (makeLenses)
import Data.UUID
import Aftok.Time (Days(..))
newtype BillableId = BillableId UUID deriving (Show, Eq)
data BillingFrequency
= Annually
| Monthly Int
| SemiMonthly
| Weekly Int
makeLenses ''BillingFrequency
data Recurrence
= Recurring { _frequency :: BillingFrequency }
| OneTime
makeLenses ''Recurrence
data Billable (p :: *) (c :: *) = Billable
{ _project :: p
, _name :: Text
, _description :: Text
, _recurrence :: Recurrence
, _amount :: c
, _gracePeriod :: Days
}
makeLenses ''Billable
monthly :: BillingFrequency
monthly = Monthly 1
bimonthly :: BillingFrequency
bimonthly = Monthly 2
quarterly :: BillingFrequency
quarterly = Monthly 3
seminannually :: BillingFrequency
seminannually = Monthly 6
annually :: BillingFrequency
annually = Annually
btcAddrParser f v = BtcAddr <$> fromField f v
btcAddrParser f v = do
addrMay <- parseBtcAddr <$> fromField f v
let err = ConversionFailed { errSQLType = "text"
, errSQLTableOid = tableOid f
, errSQLField = maybe "" B.unpack (name f)
, errHaskellType = "BtcAddr"
, errMessage = "could not deserialize value to a valid BTC address"
}
maybe (conversionError err) pure addrMay
let err = UnexpectedNull (B.unpack tn)
(tableOid f)
(maybe "" B.unpack (name f))
"UTCTime -> LogEvent"
"columns of type event_t should not contain null values"
let err = UnexpectedNull { errSQLType = B.unpack tn
, errSQLTableOid = tableOid f
, errSQLField = maybe "" B.unpack (name f)
, errHaskellType = "UTCTime -> LogEvent"
, errMessage = "columns of type event_t should not contain null values"
}
let err = Incompatible (B.unpack tn)
(tableOid f)
(maybe "" B.unpack (name f))
"UTCTime -> LogEvent"
"column was not of type event_t"
let err = Incompatible { errSQLType = B.unpack tn
, errSQLTableOid = tableOid f
, errSQLField = maybe "" B.unpack (name f)
, errHaskellType = "UTCTime -> LogEvent"
, errMessage = "column was not of type event_t"
}
CreateBillable :: Billable ProjectId Satoshi -> DBOp BillableId
ReadBillable :: BillableId -> DBOp (Maybe (Billable ProjectId Satoshi))
CreatePaymentRequest :: UserId -> PaymentRequest ProjectId BillableId -> DBOp PaymentRequestId
CreatePayment :: Payment PaymentRequestId UserId -> DBOp PaymentId
createBillable :: UserId -> Billable ProjectId Satoshi -> DBProg BillableId
createBillable uid b = withProjectAuth (b ^. B.project) uid $ CreateBillable b
readBillable :: BillableId -> DBProg (Maybe (Billable ProjectId Satoshi))
readBillable = fc . ReadBillable
--createPaymentRequest :: BillableId -> DBProg PaymentRequestId
--createPaymentRequest bid = do
-- billable <- readBillable bid
readPaymentHistory :: UserId -> DBProg [Payment PaymentRequestId UserId]
readPaymentHistory = error "Not yet implemented"
p (Version 1 _) v = Payouts . MS.mapKeys (CreditToAddress . BtcAddr) <$> parseJSON (Object v)
p (Version 1 _) v =
let parseKey :: String -> Parser CreditTo
parseKey k = maybe
(fail $ "Key " <> k <> " cannot be parsed as a valid BTC address.")
(pure . CreditToAddress)
(parseBtcAddr $ T.pack k)
in Payouts <$> join (traverseKeys parseKey <$> parseJSON (Object v))
{-# LANGUAGE TemplateHaskell #-}
module Aftok.Payments where
import ClassyPrelude
import Control.Lens (makeLenses)
import Data.Thyme.Clock as C
import Data.UUID
import qualified Network.Bippy.Proto as P
newtype PaymentRequestId = PaymentRequestId UUID deriving (Show, Eq)
newtype PaymentId = PaymentId UUID deriving (Show, Eq)
data PaymentRequest (p :: *) (b :: *) = PaymentRequest
{ _project :: p
, _paymentRequest :: P.PaymentRequest
, _paymentRequestDate :: C.UTCTime
, _billable :: b
}
makeLenses ''PaymentRequest
data Payment r u = Payment
{ _request :: r
, _payment :: P.Payment
, _paymentDate :: C.UTCTime
, _payor :: u
}
makeLenses ''Payment
module Aftok.Time where
import ClassyPrelude
newtype Days = Days Int
traverseKeys :: (Ord k, Applicative f) => (a -> f k) -> Map a b -> f (Map k b)
traverseKeys f m =
let insf a b m' = flip insert b <$> f a <*> m'
in foldrWithKey insf (pure M.empty) m
parseBtcAddr = Just . BtcAddr -- FIXME: perform validation
parseBtcAddr addr = BtcAddr <$> (base58ToAddr . encodeUtf8) addr
instance FromJSON BtcAddr where
parseJSON v = do
t <- parseJSON v
maybe (fail $ show t <> " is not a valid BTC address") pure $ parseBtcAddr t
Description: Create tables for persistence of billable & payments data.
Created: 2016-12-31 03:45:38.125915 UTC
Depends: 2016-10-13_05-36-55_user-event-log
Apply: |
create type aftok_event_t as enum(
'create_user',
'create_project',
'add_user_to_project',
'create_invitation',
'accept_invitation',
'create_event',
'amend_event',
'create_auction',
'create_bid',
'create_billable',
'create_payment_request',
'create_payment'
);
-- a log of raw events - the current state of the database
-- should be reproducible by replaying the entire history of
-- events
create table if not exists aftok_events (
id uuid primary key default uuid_generate_v4(),
event_time timestamp with time zone not null,
event_type aftok_event_t not null,
event_json json not null
);
create type recurrence_t as enum ('onetime', 'weekly', 'semimonthly', 'monthly', 'annually');
create table if not exists billables (
id uuid primary key default uuid_generate_v4(),
project_id uuid not null references projects(id),
event_id uuid not null references aftok_events(id),
name text not null,
description text,
recurrence_type recurrence_t not null,
recurrence_count int,
billing_amount numeric not null,
grace_period_days int not null
);
create table if not exists subscriptions (
id uuid primary key default uuid_generate_v4(),
user_id uuid not null references users(id),
billable_id uuid not null references billables(id),
event_id uuid not null references aftok_events(id)
);
create table if not exists payment_requests (
id uuid primary key default uuid_generate_v4(),
subscription_id uuid not null references subscriptions(id),
event_id uuid not null references aftok_events(id),
request_data bytea not null
);
create table if not exists payments (
id uuid primary key default uuid_generate_v4(),
payment_request_id uuid not null references payment_requests(id),
event_id uuid not null references aftok_events(id),
payment_data bytea not null
);
Revert: |
drop table payments;
drop table payment_requests;
drop table subscriptions;
drop table billables;
drop table aftok_events;
module Aftok.Snaplet.Payments where
import ClassyPrelude
import Network.Bippy
import Network.Bippy.Types
import Snap.Core
import Snap.Snaplet
requestPaymentHandler :: Handler App App
requestPaymentHandler = do
-- get payout percentages from payouts handler
uid <- requireUserId
pid <- requireProjectId
ptime <- liftIO $ C.getCurrentTime
payouts <- snapEval $ fc (ReadWorkIndex pid)
pure $ payouts (toDepF $ project ^. depf) ptime widx
-- look up the outstanding
undefined
let parseUser = User <$> (UserName <$> v .: "username")
<*> (BtcAddr <$> v .: "btcAddr")
<*> (Email <$> v .: "email")
let parseUser = User <$> (UserName <$> v .: "username")
<*> (parseBtcAddr <$> v .: "btcAddr")
<*> (Email <$> v .: "email")
extra-deps:
- snaplet-postgresql-simple-0.6.0.4
- location:
git: git@github.com:aftok/bippy.git
commit: 6284d5fff3954e0e52d559298364035a220867af
extra-dep: true
allow-newer: true
extra-deps:
- aeson-0.11.2.1
- base-orphans-0.5.4
- bytestring-builder-0.10.8.1.0
- call-stack-0.1.0
- haskoin-core-0.4.0
- hspec-2.3.2
- hspec-core-2.3.2
- hspec-discover-2.3.2
- hspec-expectations-0.8.2
- mmorph-1.0.9
- mono-traversable-0.10.2
- murmur3-1.0.1
- pbkdf-1.1.1.1
- postgresql-libpq-0.9.2.0
- postgresql-simple-0.5.2.1