Browse Source

User Pay Payments for Membership and Makes Purchases for Products

Maxim Sokhatsky 12 years ago
parent
commit
090bedb2fd

+ 0 - 0
include/attachment.hrl → include/attachments.hrl


+ 13 - 0
include/membership.hrl

@@ -0,0 +1,13 @@
+
+-type payment_type():: credit_card | mobile | paypal | wire_transfer | facebook.
+
+-record(membership, {
+        id                         :: any(),      % package id
+        payment_type               :: payment_type(),
+        no                         :: integer(),  % package number (need to display in pricelist)
+        amount                     :: integer(),  % price
+        currency                   :: integer(),  % currency charge
+        fee                        :: integer(),  % net membership fee
+        available_for_sale = false :: boolean(),  % not used yet
+        quota::integer() }).
+

+ 0 - 62
include/membership_packages.hrl

@@ -1,62 +0,0 @@
--type payment_type():: credit_card | mobile | paypal | wire_transfer | facebook.
--type purchase_state() :: added | done | cancelled | pending | untracked.
-
--record(basket, {
-        user_id                    ::any(),
-        package_id                 ::any()}).
-
--record(membership_package, {
-        id                         ::any(),      % package id
-        payment_type               ::payment_type(),
-        no                         ::integer(),  % package number (need to display in pricelist)
-        amount                     ::integer(),  % price
-        deducted_for_gifts         ::integer(),  % kakush currency charge
-        net_membership             ::integer(),  % net membership fee
-        available_for_sale = false ::boolean(),  % not used yet
-        quota::integer()                         % game quota
-        }).
-
--record(state_change, {
-        time     :: erlang:now(),
-        state    :: any(),
-        info     :: any()}).
-
--record(membership_purchase, {
-        id                    :: any(),
-        external_id           :: any(),     % id of the purchase in external payment system if any
-        user_id               :: any(),
-        state                 :: purchase_state(),
-        membership_package    :: #membership_package{},
-        next                  :: any(),
-        prev                  :: any(),
-        start_time            :: erlang:now(),
-        end_time              :: erlang:now(),
-        state_log = []        :: [#state_change{}],
-        info                  :: any()      % payment-specific info about purchase if any
-        }).
-
--record(user_purchase, {
-        user :: any(),
-        top           :: any()
-        }).
-
--define(MP_STATE_ADDED,     added).
--define(MP_STATE_DONE,      done).
--define(MP_STATE_CANCELLED, cancelled).
--define(MP_STATE_UNTRACKED, untracked).
--define(MP_STATE_PENDING,   pending).
--define(MP_STATE_FAILED,    failed).
--define(MP_STATE_UNKNOWN,   unknown).
--define(MP_STATE_CONFIRMED, confirmed).
--define(MP_STATE_UNEXPECTED, unexpected).
--define(MP_STATE_DISCARDED, discarded).
--define(MP_MONTHLY_LIMIT_MULTIPLIER, 3).
-
--record(pi_credit_card, {
-        cardholder_name,
-        cardholder_surname,
-        cardnumber_masked,
-        retref_num,
-        prov_date,
-        auth_code
-        }).

+ 46 - 0
include/payments.hrl

@@ -0,0 +1,46 @@
+
+-type payment_state() :: added | done | cancelled | pending | untracked |
+                         failed | unknown | confirmed | discarded.
+
+-record(state_change, {
+        time     :: erlang:now(),
+        state    :: any(),
+        info     :: any()}).
+
+-record(payment, {
+        id              :: any(),
+        external_id     :: any(),     % id of the purchase in external payment system if any
+        user_id         :: any(),
+        state           :: payment_state(),
+        membership      :: #membership{},
+        next            :: any(),
+        prev            :: any(),
+        start_time      :: erlang:now(),
+        end_time        :: erlang:now(),
+        state_log = []  :: [#state_change{}],
+        info            :: any() }).
+
+-record(user_payment, {
+        user :: any(),
+        top   :: any() }).
+
+-define(MP_STATE_ADDED,      added).
+-define(MP_STATE_DONE,       done).
+-define(MP_STATE_CANCELLED,  cancelled).
+-define(MP_STATE_UNTRACKED,  untracked).
+-define(MP_STATE_PENDING,    pending).
+-define(MP_STATE_FAILED,     failed).
+-define(MP_STATE_UNKNOWN,    unknown).
+-define(MP_STATE_CONFIRMED,  confirmed).
+-define(MP_STATE_UNEXPECTED, unexpected).
+-define(MP_STATE_DISCARDED,  discarded).
+-define(MP_MONTHLY_LIMIT_MULTIPLIER, 3).
+
+-record(pi_credit_card, {
+        cardholder_name,
+        cardholder_surname,
+        cardnumber_masked,
+        retref_num,
+        prov_date,
+        auth_code
+        }).

+ 9 - 0
include/purchases.hrl

@@ -0,0 +1,9 @@
+-record(user_products, {
+        username,
+        timestamp,
+        product_id }).
+
+-record(user_basket, {
+        username,
+        timestamp,
+        product_id }).

+ 0 - 5
include/users.hrl

@@ -88,11 +88,6 @@
 -record(user_ignores, {who, whom}).
 -record(user_ignores_rev, {whom, who}).
 
--record(user_bought_gifts, {
-        username,
-        timestamp,
-        gift_id }).
-
 -record(user_count, {count}).
 -record(twitter_oauth, {user_id, token, secret}).
 -record(facebook_oauth, {user_id, access_token}).

+ 6 - 4
src/kvs.erl

@@ -10,7 +10,8 @@
 -include_lib("kvs/include/config.hrl").
 -include_lib("kvs/include/accounts.hrl").
 -include_lib("kvs/include/log.hrl").
--include_lib("kvs/include/membership_packages.hrl").
+-include_lib("kvs/include/membership.hrl").
+-include_lib("kvs/include/payments.hrl").
 -include_lib("stdlib/include/qlc.hrl").
 -include_lib("kvs/include/feed_state.hrl").
 -compile(export_all).
@@ -74,8 +75,8 @@ add_purchases() ->
     ok.
 
 add_purchase(UserId, Package) ->
-    {ok, MPId} = kvs_membership:add_purchase(#membership_purchase{user_id=UserId, membership_package=Package}),
-    kvs_membership:set_purchase_state(MPId, ?MP_STATE_DONE, undefined).
+    {ok, MPId} = kvs_payment:add_payment(#payment{user_id=UserId, membership=Package}),
+    kvs_payment:set_payment_state(MPId, ?MP_STATE_DONE, undefined).
 
 add_seq_ids() ->
     Init = fun(Key) ->
@@ -87,7 +88,8 @@ add_seq_ids() ->
     Init("meeting"),
     Init("user_transaction"),
     Init("transaction"),
-    Init("membership_purchase"),
+    Init("membership"),
+    Init("payment"),
     Init("acl"),
     Init("acl_entry"),
     Init("feed"),

+ 26 - 90
src/kvs_account.erl

@@ -1,65 +1,40 @@
 -module(kvs_account).
 -include_lib("kvs/include/accounts.hrl").
--include_lib("kvs/include/membership_packages.hrl").
+-include_lib("kvs/include/membership.hrl").
+-include_lib("kvs/include/payments.hrl").
 -include_lib("kvs/include/log.hrl").
 -include_lib("kvs/include/feed_state.hrl").
+-compile(export_all).
 
--export([debet/2, credit/2, balance/2, create_account/1, create_account/2]).
--export([transaction/4, transaction/5]).
--export([check_quota/1, check_quota/2]).
--export([user_paid/1]).
-
--spec transaction(string(), currency(), integer(), transaction_info()) -> {ok, transaction_id()} | {error, term()}.
-transaction(Account, Currency, 0, TransactionInfo) -> 
-    ?WARNING("zero transaction: Account=~p, Currency=~p,TransactionInfo=~p", [Account, Currency, TransactionInfo]), 
-    ok;
+transaction(Account, Currency, 0, TransactionInfo) -> ok;
 transaction(Account, Currency, Amount, TransactionInfo) when Amount /= 0->
-    {Remitter, Acceptor} = if Amount > 0 -> {system, Account};
-                                    true -> {Account, system} end,
+    {Remitter, Acceptor} = if Amount > 0 -> {system, Account}; true -> {Account, system} end,
     transaction(Remitter, Acceptor, Currency, abs(Amount), TransactionInfo).
-
-%% @doc Add new transaction, will change state of accouts. All transactions
-%%      are between user account and system account. So  moving of the funds
-%%      (money, kaush, game_points, etc.) should be performed with transactions.
--spec transaction(Remitter::string(), Acceptor::string(), Currency::currency(), Amount::integer(), 
-                  TransactionInfo::transaction_info()) -> {ok, transaction_id()} | {error, any()}.
 transaction(Remitter, Acceptor, Currency, Amount, TransactionInfo) ->
     TX = #transaction{id = generate_id(), acceptor = Acceptor, remitter = Remitter,
                       amount = Amount, commit_time = now(), currency = Currency, info = TransactionInfo},
     commit_transaction(TX).
 
-%% @doc How many funds got from the start of the time.
--spec debet(account_id(), currency()) -> {ok, integer()} | {error, term()}.
 debet(Account, Currency) ->
-    case get_account(Account, Currency) of
-         #account{debet = Debet} -> {ok, Debet};
-         Error -> Error
-    end.
+    case kvs:get(account,{Account, Currency}) of
+         {ok,#account{debet = Debet}} -> {ok, Debet};
+         Error -> Error end.
 
-%% @doc How many funds spent  from the start of the time.
--spec credit(account_id(), currency()) -> {ok, integer()} | {error, term()}.
 credit(AccountId, Currency) ->
-    case get_account(AccountId, Currency) of
-         #account{credit = Credit} -> {ok, Credit};
-         Error -> Error
-    end.
+    case kvs:get(account,{AccountId, Currency}) of
+         {ok,#account{credit = Credit}} -> {ok, Credit};
+         Error -> Error end.
 
-%% @doc balance of the given account by given currency
--spec balance(account_id(), currency()) -> {ok, integer()} | {error, term()}.
 balance(AccountId, Currency) ->
-    case get_account(AccountId, Currency) of
-         #account{debet = Debet, credit = Credit} -> {ok, Debet - Credit};
-         Error -> Error
-    end.
+    case kvs:get(account,{AccountId, Currency}) of
+         {ok, #account{debet = Debet, credit = Credit}} -> {ok, Debet - Credit};
+         Error -> Error end.
 
-%% @doc Create account with for all supported currencies.
--spec create_account(Username::string()|system) -> ok | {error, term()}.
 create_account(AccountId) ->
     Currencies = get_currencies(),
     try [{ok, Currency} = {create_account(AccountId, Currency), Currency} || Currency <- Currencies]
     catch _:_ -> {error, unable_create_account} end.
 
-%% @doc Create account for specified currency.
 create_account(AccountId, Currency) ->
     Account = #account{id = {AccountId, Currency},
                        credit = 0, debet = 0, last_change = 0},
@@ -67,19 +42,9 @@ create_account(AccountId, Currency) ->
     case kvs:put(Account) of
          ok -> ok;
          Error -> ?ERROR("create_account: put to db error: ~p", [Error]),
-                  {error, unable_to_store_account}
-    end.
-
-%% @doc Check quota balance against hard and soft limits
--spec check_quota(Username::string()) -> ok | {error, soft_limit} | {error, hard_limit}.
-
-check_quota(User) ->
-    check_quota(User, 0).
-
-%% @doc Check quota balance against hard and soft limit. Second argument is
-%%      Amount planning to be cherged from user.
--spec check_quota(User::string(), Amount::integer()) -> ok | {error, soft_limit} | {error, hard_limit}.
+                  {error, unable_to_store_account} end.
 
+check_quota(User) -> check_quota(User, 0).
 check_quota(User, Amount) ->
     SoftLimit = kvs:get_config("accounts/quota_limit/soft",  -20),
     {ok, Balance} = balance(User, quota),
@@ -114,58 +79,29 @@ commit_transaction(#transaction{remitter = R, acceptor = A,  currency = Currency
     end.
 
 change_accounts(Remitter, Acceptor, Currency, Amount) ->
-    case {get_account(Remitter, Currency), get_account(Acceptor, Currency)} of
-        {RA = #account{}, AA = #account{}}  ->
+    case {kvs:get(account,{Remitter, Currency}), kvs:get(account,{Acceptor, Currency})} of
+        {{ok, RA = #account{}}, {ok, AA = #account{}}}  ->
             ?INFO("transacrion: RemitterAccount ~p, AcceptorAccount: ~p", [RA, AA]),
             %% check balance for remitter according to currency and amount
             case check_remitter_balance(RA, Amount) of
-                %% all ok write changes
-                ok -> %% increase credit of remmitter, last change is less then zero
-                    RA1 = RA#account{credit = RA#account.credit + Amount,
-                    last_change = -Amount },
-                    %% increase debet of acceptor, last change is positive
-                    AA1 = AA#account{debet = AA#account.debet + Amount,
-                    last_change = Amount},
-                    kvs:put([AA1, RA1]);
-                {error, Reason} ->
-                    {error, {remitter_balance, Reason}}
-               end;
-        {{error, Reason}, #account{}} ->
-            {error, {remitter_account_unavailable, Reason}};
-        {#account{}, {error, Reason}} ->
-            {error, {acceptor_account_unavailable, Reason}};
-        {RE, AE} ->
-            {error, both_accounts_unavailable}
-    end.
+                ok ->   RA1 = RA#account{credit = RA#account.credit + Amount, last_change = -Amount },
+                        AA1 = AA#account{debet = AA#account.debet + Amount, last_change = Amount},
+                        kvs:put([AA1, RA1]);
+                {error, Reason} -> {error, {remitter_balance, Reason}} end;
+        {{error, Reason}, #account{}} -> {error, {remitter_account_unavailable, Reason}};
+        {#account{}, {error, Reason}} -> {error, {acceptor_account_unavailable, Reason}};
+        {RE, AE} -> {error, both_accounts_unavailable} end.
 
 check_remitter_balance(#account{id = {system, _}}, _) -> ok;
 check_remitter_balance(_Account, _Amount) -> ok.
 
-get_account(Account, Currency) ->
-    case kvs:get(account, {Account, Currency}) of
-         {ok, #account{} = AR} -> AR;
-         _ -> {error, account_not_found}
-    end.
-
-get_currencies() -> [internal,
-                     currency,
-                     money,
-                     quota,
-                     points].
+get_currencies() -> [internal, currency, money, quota, points].
 
 generate_id() ->
     {MegSec, Sec, MicroSec} = now(),
     H = erlang:phash2(make_ref()),
     lists:concat([MegSec*1000000000000, Sec*1000000, MicroSec, "-", H]).
 
-user_paid(UId) ->
-    {_, UP} = kvs:get(user_purchase, UId),
-    case UP of
-        notfound -> false;
-        #user_purchase{top = undefined} -> false;
-        _ -> true
-    end.
-
 transactions(UserId) -> tx_list(UserId, undefined, 10000).
 
 tx_list(UserId, undefined, PageAmount) ->

+ 32 - 191
src/kvs_membership.erl

@@ -1,6 +1,7 @@
 -module(kvs_membership).
 -author('Vladimir Baranov <baranoff.vladimir@gmail.com>').
--include_lib("kvs/include/membership_packages.hrl").
+-include_lib("kvs/include/membership.hrl").
+-include_lib("kvs/include/payments.hrl").
 -include_lib("kvs/include/log.hrl").
 -include_lib("kvs/include/accounts.hrl").
 -include_lib("kvs/include/feed_state.hrl").
@@ -9,180 +10,51 @@
 -type package_id() :: integer().
 -type list_options()::[{payment_type, payment_type()}|{available_for_sale, boolean()}].
 
-add_package(#membership_package{}=Package)->
+add_package(#membership{}=Package)->
     Id = generate_id(),
-    save_package(Package#membership_package{id = Id}).
-
-get_package(PackageId)->
-    case kvs:get(membership_package, PackageId) of
-        {ok, #membership_package{} = Package}-> {ok, Package};
-        {error, Reason}-> {error, Reason} end.
+    save_package(Package#membership{id = Id}).
 
 list_packages(Options) ->
-    Predicate = fun(MP = #membership_package{}) -> check_conditions(Options, MP, true) end,
-    select(membership_package, Predicate).
+    Predicate = fun(MP = #membership{}) -> check_conditions(Options, MP, true) end,
+    select(membership, Predicate).
 
-list_packages()-> kvs:all(membership_package).
+list_packages()-> kvs:all(membership).
 
 available_for_sale(PackageId, State) ->
-    case get_package(PackageId) of
-        {ok, Package} -> case save_package(Package#membership_package{available_for_sale = State}) of
+    case kvs:get(membership,PackageId) of
+        {ok, Package} -> case save_package(Package#membership{available_for_sale = State}) of
             {ok, _} -> ok;
             Error -> Error end;
         {error, Reason}-> {error, Reason} end.
 
-add_purchase(#membership_purchase{} = MP) -> add_purchase(#membership_purchase{} = MP, undefined, undefined).
-
-add_purchase(#membership_purchase{} = MP, State0, Info) ->
-    case kvs:get(membership_purchase, MP#membership_purchase.id) of
-        {ok, _} -> {error, already_bought_that_one};
-        {error, notfound} ->
-            %% fill needed fields
-            Start = now(),
-            State = default_if_undefined(State0, undefined, ?MP_STATE_ADDED),
-            %% FIXME: uniform info field if needed
-            StateLog = case Info of
-                           undefined ->
-                               [#state_change{time = Start, state = State,
-                                              info = system_change}];
-                           _ ->
-                               [#state_change{time = Start, state = State,
-                                              info = Info}]
-                       end,
-
-            %% TODO: add check for duplicate purches if id given by external module
-            Id = default_if_undefined(MP#membership_purchase.id, undefined, purchase_id()),
-
-            Purchase = MP#membership_purchase{id = Id,
-                                              state = State,
-                                              start_time = Start,
-                                              state_log = StateLog},
-
-            %% notify about purchase added
-%            mqs:notify_purchase(Purchase),
-
-            ?INFO("Purchase added ~p ~p",[Purchase#membership_purchase.user_id, Purchase]),
-
-            store_riak:add_purchase_to_user(Purchase#membership_purchase.user_id, Purchase)
-    end.
-
-get_purchase(PurchaseId)->
-    case kvs:get(membership_purchase, PurchaseId) of
-        {ok, #membership_purchase{} = Package}-> {ok, Package};
-        {error, Reason}-> {error, Reason} end.
-
-set_purchase_state(MPId, NewState, Info) ->
-    case kvs:get(membership_purchase, MPId) of 
-      {ok, MP} ->
-
-    Time = now(),
-    StateLog = MP#membership_purchase.state_log,
-    %% add new state to head of state log
-    NewStateLog = [#state_change{time = Time, state = NewState,
-                                 info = Info}|StateLog],
-    EndTime = case NewState of
-                  ?MP_STATE_DONE -> now();
-                  ?MP_STATE_CANCELLED -> now();
-                  ?MP_STATE_FAILED -> now();
-                  _ -> MP#membership_purchase.end_time
-              end,
-    Purchase = MP#membership_purchase{state = NewState,
-                                      end_time = EndTime,
-                                      state_log = NewStateLog},
-
-    %% notify aboput state change
-%    mqs:notify_purchase(Purchase),
-    NewMP=MP#membership_purchase{state = NewState,
-                                         end_time = EndTime,
-                                         state_log = NewStateLog},
-    kvs:put(NewMP),
-
-    if
-        NewState == ?MP_STATE_DONE ->
-            charge_user_account(MP);
-%            affiliates:purchase_hook(NewMP);
-        true ->
-            ok
-    end,
-    ok;
-  
-    Error -> ?INFO("Can't set purchase state, not yet in db"), Error
-    end.
-
-set_purchase_info(MPId, Info) ->
-    {ok, MP} = kvs:get(membership_purchase, MPId),
-    kvs:put(MP#membership_purchase{info = Info}).
-
-set_purchase_external_id(MPId, ExternalId) ->
-    {ok, MP} = kvs:get(membership_purchase, MPId),
-    case MP#membership_purchase.external_id of
-        ExternalId -> ok;
-        _ -> kvs:put(MP#membership_purchase{external_id = ExternalId}) end.
-
-list_purchases() -> kvs:all(membership_purchase).
-
-list_purchases(SelectOptions) ->
-    Predicate = fun(MP = #membership_purchase{}) -> check_conditions(SelectOptions, MP, true) end,
-    select(membership_purchase, Predicate).
-
-purchase_id() ->
-    NextId = kvs:next_id("membership_purchase"),
-    lists:concat([timestamp(), "_", NextId]).
+generate_id()->
+    Id = kvs:next_id("membership"),
+    integer_to_list(Id).
 
 add_sample_data()->
     SamplePackages = [
-    #membership_package{no = 1, amount = 7,   deducted_for_gifts = 0,  quota = 7,   net_membership = 7},
-    #membership_package{no = 2, amount = 12,  deducted_for_gifts = 5,  quota = 15,  net_membership = 7},
-    #membership_package{no = 3, amount = 12,  deducted_for_gifts = 0,  quota = 15,  net_membership = 12},
-    #membership_package{no = 4, amount = 25,  deducted_for_gifts = 10, quota = 30,  net_membership = 15},
-    #membership_package{no = 5, amount = 30,  deducted_for_gifts = 0,  quota = 60,  net_membership = 30},
-    #membership_package{no = 6, amount = 50,  deducted_for_gifts = 20, quota = 60,  net_membership = 30},
-    #membership_package{no = 7, amount = 50,  deducted_for_gifts = 0,  quota = 90,  net_membership = 50},
-    #membership_package{no = 8, amount = 100, deducted_for_gifts = 40, quota = 120, net_membership = 60}],
+    #membership{no = 1, amount = 7,   currency = 0,  quota = 7,   fee = 7},
+    #membership{no = 2, amount = 12,  currency = 5,  quota = 15,  fee = 7},
+    #membership{no = 3, amount = 12,  currency = 0,  quota = 15,  fee = 12},
+    #membership{no = 4, amount = 25,  currency = 10, quota = 30,  fee = 15},
+    #membership{no = 5, amount = 30,  currency = 0,  quota = 60,  fee = 30},
+    #membership{no = 6, amount = 50,  currency = 20, quota = 60,  fee = 30},
+    #membership{no = 7, amount = 50,  currency = 0,  quota = 90,  fee = 50},
+    #membership{no = 8, amount = 100, currency = 40, quota = 120, fee = 60}],
     WithPaymentTypes = [
-        Package#membership_package{id = generate_id(), payment_type=Payment} ||
+        Package#membership{id = generate_id(), payment_type=Payment} ||
             Payment <- [facebook, credit_card, wire_transfer, paypal, mobile],
             Package <- SamplePackages],
-    Enabled = [P#membership_package{available_for_sale = true} || P <- WithPaymentTypes],
+    Enabled = [P#membership{available_for_sale = true} || P <- WithPaymentTypes],
     kvs:put(Enabled).
 
-generate_id()->
-    Id = kvs:next_id("membership_package"),
-    integer_to_list(Id).
-
-default_if_undefined(Value, Undefined, Default) ->
-    case Value of
-        Undefined -> Default;
-        _ -> Value end.
-
-charge_user_account(MP) ->
-    OrderId = MP#membership_purchase.id,
-    Package = MP#membership_purchase.membership_package,
-    Kakush = Package#membership_package.deducted_for_gifts,
-    Quota = Package#membership_package.quota,
-    UserId = MP#membership_purchase.user_id,
-
-    PaymentTransactionInfo = #tx_payment{id=MP#membership_purchase.id},
-
-    try
-        ?INFO("charge user account. OrderId: ~p, User: ~p, Kakush:~p, Quota:~p",
-              [OrderId, UserId, Kakush, Quota]),
-
-        kvs_account:transaction(UserId, internal, Kakush, PaymentTransactionInfo),
-        kvs_account:transaction(UserId, quota, Quota, PaymentTransactionInfo)
-    catch
-        _:E ->
-            ?ERROR("unable to charge user account. User=~p, OrderId=~p. Error: ~p",
-                   [UserId, OrderId, E])
-    end.
-
 select(RecordType, Predicate) ->
     All = kvs:all(RecordType),
     lists:filter(Predicate, All).
 
 save_package(Package) ->
     case kvs:put([Package]) of
-        ok -> {ok, Package#membership_package.id};
+        ok -> {ok, Package#membership.id};
         {error, Reason}-> {error, Reason} end.
 
 timestamp()->
@@ -191,56 +63,25 @@ timestamp()->
     lists:flatten(io_lib:format("~b~2..0b~2..0b_~2..0b~2..0b~2..0b", [Y, Mn, D, H, M, S])).
 
 check_conditions(_, _, false) -> false;
-check_conditions([{available_for_sale, AS}|T], MP = #membership_package{available_for_sale = AS1}, _) -> check_conditions(T, MP, AS == AS1);
-check_conditions([{payment_type, PT}|T], MP = #membership_package{payment_type = PT1}, _) -> check_conditions(T, MP, PT == PT1);
+check_conditions([{available_for_sale, AS}|T], MP = #membership{available_for_sale = AS1}, _) -> check_conditions(T, MP, AS == AS1);
+check_conditions([{payment_type, PT}|T], MP = #membership{payment_type = PT1}, _) -> check_conditions(T, MP, PT == PT1);
 check_conditions([], _, true) -> true.
 
-delete_package(PackageId) -> kvs:delete(membership_package, PackageId).
+delete_package(PackageId) -> kvs:delete(membership, PackageId).
 
+%% MQ API
 
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-handle_notice(["system", "add_package"] = Route,
+handle_notice(["kvs_membership","system", "add_package"] = Route,
     Message, #state{owner = Owner, type =Type} = State) ->
     ?INFO("queue_action(~p): add_package: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),
     {MP} = Message,
-    case kvs_membership:add_package(MP) of
-        {ok, _} ->
-            ok;
-        {error, Reason} ->
-            ?ERROR("Unable to add membership package: ~p, Reason ~p", [MP, Reason])
+    case add_package(MP) of
+        {ok, _} -> ok;
+        {error, Reason} -> ?ERROR("Unable to add membership package: ~p, Reason ~p", [MP, Reason])
     end,
     {noreply, State};
 
-handle_notice(["purchase", "user", _, "set_purchase_state"] = Route,
-    Message, #state{owner = Owner, type =Type} = State) ->
-    ?INFO("queue_action(~p): set_purchase_state: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),  
-    {MPId, NewState, Info} = Message,
-    kvs_membership:set_purchase_state(MPId, NewState, Info),
-    {noreply, State};
-
-handle_notice(["purchase", "user", _, "add_purchase"] = Route,
-    Message, #state{owner = Owner, type =Type} = State) ->
-    ?INFO("queue_action(~p): add_purchase: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),    
-    {MP} = Message,
-    kvs_membership:add_purchase(MP),
-    {noreply, State};
-
-handle_notice(["purchase", "user", _, "set_purchase_external_id"] = Route,
-    Message, #state{owner = Owner, type =Type} = State) ->
-    ?INFO("queue_action(~p): set_purchase_external_id: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),
-    {PurchaseId, TxnId} = Message,
-    kvs_membership:set_purchase_external_id(PurchaseId, TxnId),
-    {noreply, State};
-
-handle_notice(["purchase", "user", _, "set_purchase_info"] = Route,
-    Message, #state{owner = Owner, type =Type} = State) ->
-    ?INFO("queue_action(~p): set_purchase_info: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),
-    {OrderId, Info} = Message,
-    kvs_membership:set_purchase_info(OrderId, Info),
-    {noreply, State};
-
-handle_notice(Route, Message, State) -> error_logger:info_msg("Unknown PAYMENTS notice").
+handle_notice(Route, Message, State) -> error_logger:info_msg("Unknown MEMBERSHIP notice").
 
 coalesce(undefined, B) -> B;
 coalesce(A, _) -> A.

+ 164 - 0
src/kvs_payment.erl

@@ -0,0 +1,164 @@
+-module(kvs_payment).
+-include_lib("kvs/include/membership.hrl").
+-include_lib("kvs/include/payments.hrl").
+-include_lib("kvs/include/log.hrl").
+-include_lib("kvs/include/accounts.hrl").
+-include_lib("kvs/include/feed_state.hrl").
+-compile(export_all).
+
+user_paid(UId) ->
+    {_, UP} = kvs:get(user_payment, UId),
+    case UP of
+        notfound -> false;
+        #user_payment{top = undefined} -> false;
+        _ -> true
+    end.
+
+default_if_undefined(Value, Undefined, Default) ->
+    case Value of
+        Undefined -> Default;
+        _ -> Value end.
+
+charge_user_account(MP) ->
+
+    OrderId = MP#payment.id,
+    Package = MP#payment.membership,
+    UserId  = MP#payment.user_id,
+
+    Currency = Package#membership.currency,
+    Quota    = Package#membership.quota,
+
+    PaymentTransactionInfo = #tx_payment{id=MP#payment.id},
+
+    try
+        kvs_account:transaction(UserId, currency, Currency, PaymentTransactionInfo),
+        kvs_account:transaction(UserId, quota,    Quota,    PaymentTransactionInfo)
+    catch
+        _:E ->
+            ?ERROR("unable to charge user account. User=~p, OrderId=~p. Error: ~p",
+                   [UserId, OrderId, E])
+    end.
+
+add_payment(#payment{} = MP) -> add_payment(#payment{} = MP, undefined, undefined).
+add_payment(#payment{} = MP, State0, Info) ->
+    case kvs:get(payment, MP#payment.id) of
+        {ok, _} -> {error, already_bought_that_one};
+        {error, notfound} ->
+            Start = now(),
+            State = default_if_undefined(State0, undefined, ?MP_STATE_ADDED),
+            StateLog = case Info of
+                           undefined -> [#state_change{time = Start, state = State, info = system_change}];
+                           _ -> [#state_change{time = Start, state = State, info = Info}] end,
+
+            Id = default_if_undefined(MP#payment.id, undefined, payment_id()),
+            Purchase = MP#payment{id = Id, state = State, start_time = Start, state_log = StateLog},
+            %mqs:notify_purchase(Purchase),
+            ?INFO("Purchase added ~p ~p",[Purchase#payment.user_id, Purchase]),
+            add_to_user(Purchase#payment.user_id, Purchase)
+    end.
+
+add_to_user(UserId,Payment) ->
+    {ok,Team} = case kvs:get(user_payment, UserId) of
+                     {ok,T} -> ?ERROR("user_payment found"), {ok,T};
+                     _ -> ?ERROR("user_payment not found"),
+                          Head = #user_payment{ user = UserId, top = undefined},
+                          {kvs:put(Head),Head}
+                end,
+
+    EntryId = Payment#payment.id,
+    Prev = undefined,
+    case Team#user_payment.top of
+        undefined -> Next = undefined;
+        X -> case kvs:get(payment, X) of
+                {ok, TopEntry} ->
+                     Next = TopEntry#payment.id,
+                     EditedEntry = TopEntry#payment{next = TopEntry#payment.next, prev = EntryId},
+                     kvs:put(EditedEntry);
+                {error,notfound} -> Next = undefined end
+    end,
+
+    kvs:put(#user_payment{ user = UserId, top = EntryId}), % update team top with current
+
+    Entry  = Payment#payment{id = EntryId, user_id = UserId, next = Next, prev = Prev},
+    case kvs:put(Entry) of ok -> {ok, EntryId};
+                           Error -> ?INFO("Cant write purchase"), {failure,Error} end.
+
+set_payment_state(MPId, NewState, Info) ->
+    case kvs:get(payment, MPId) of 
+      {ok, MP} ->
+
+    Time = now(),
+    StateLog = MP#payment.state_log,
+    NewStateLog = [#state_change{time = Time, state = NewState, info = Info}|StateLog],
+    EndTime = case NewState of
+                  ?MP_STATE_DONE -> now();
+                  ?MP_STATE_CANCELLED -> now();
+                  ?MP_STATE_FAILED -> now();
+                  _ -> MP#payment.end_time
+              end,
+    Purchase = MP#payment{state = NewState, end_time = EndTime, state_log = NewStateLog},
+
+%    mqs:notify_purchase(Purchase),
+    NewMP=MP#payment{state = NewState, end_time = EndTime, state_log = NewStateLog},
+    kvs:put(NewMP),
+
+    if
+        NewState == ?MP_STATE_DONE -> charge_user_account(MP); % affiliates:purchase_hook(NewMP);
+        true -> ok
+    end,
+
+    ok;
+
+    Error -> ?INFO("Can't set purchase state, not yet in db"), Error
+    end.
+
+set_payment_info(MPId, Info) ->
+    {ok, MP} = kvs:get(payment, MPId),
+    kvs:put(MP#payment{info = Info}).
+
+set_payment_external_id(MPId, ExternalId) ->
+    {ok, MP} = kvs:get(payment, MPId),
+    case MP#payment.external_id of
+        ExternalId -> ok;
+        _ -> kvs:put(MP#payment{external_id = ExternalId}) end.
+
+list_payments() -> kvs:all(payment).
+
+list_payments(SelectOptions) ->
+    Predicate = fun(MP = #payment{}) -> kvs_membership:check_conditions(SelectOptions, MP, true) end,
+    kvs_membership:select(payment, Predicate).
+
+payment_id() ->
+    NextId = kvs:next_id("payment"),
+    lists:concat([kvs_membership:timestamp(), "_", NextId]).
+
+handle_notice(["kvs_payment", "user", _, "set_purchase_state"] = Route,
+    Message, #state{owner = Owner, type =Type} = State) ->
+    ?INFO("queue_action(~p): set_purchase_state: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),  
+    {MPId, NewState, Info} = Message,
+    set_payment_state(MPId, NewState, Info),
+    {noreply, State};
+
+handle_notice(["kvs_payment", "user", _, "add_purchase"] = Route,
+    Message, #state{owner = Owner, type =Type} = State) ->
+    ?INFO("queue_action(~p): add_purchase: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),    
+    {MP} = Message,
+    add_payment(MP),
+    {noreply, State};
+
+handle_notice(["kvs_payment", "user", _, "set_purchase_external_id"] = Route,
+    Message, #state{owner = Owner, type =Type} = State) ->
+    ?INFO("queue_action(~p): set_purchase_external_id: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),
+    {PurchaseId, TxnId} = Message,
+    set_payment_external_id(PurchaseId, TxnId),
+    {noreply, State};
+
+handle_notice(["kvs_payment", "user", _, "set_purchase_info"] = Route,
+    Message, #state{owner = Owner, type =Type} = State) ->
+    ?INFO("queue_action(~p): set_purchase_info: Owner=~p, Route=~p, Message=~p", [self(), {Type, Owner}, Route, Message]),
+    {OrderId, Info} = Message,
+    set_payment_info(OrderId, Info),
+    {noreply, State};
+
+handle_notice(Route, Message, State) -> error_logger:info_msg("Unknown PAYMENTS notice").
+

+ 6 - 14
src/store_riak.erl

@@ -8,15 +8,15 @@
 -include_lib("kvs/include/acls.hrl").
 -include_lib("kvs/include/invites.hrl").
 -include_lib("kvs/include/meetings.hrl").
--include_lib("kvs/include/membership_packages.hrl").
+-include_lib("kvs/include/membership.hrl").
+-include_lib("kvs/include/payments.hrl").
 -include_lib("kvs/include/accounts.hrl").
 -include_lib("kvs/include/log.hrl").
 -include_lib("stdlib/include/qlc.hrl").
 -compile(export_all).
 
-delete() -> ok.
 start() -> ok.
-stop() -> stopped.
+stop() -> ok.
 
 initialize() ->
     C = riak:client_connect(node()),
@@ -57,9 +57,6 @@ make_indices(#group_subscription{user_id=UId, group_id=GId}) -> [
     {<<"who_bin">>, key_to_bin(UId)},
     {<<"where_bin">>, key_to_bin(GId)}];
 
-make_indices(#user_bought_gifts{username=UId}) -> [
-    {<<"user_bought_gifts_username_bin">>, key_to_bin(UId)}];
-
 make_indices(#user{username=UId,zone=Zone}) -> [
     {<<"user_bin">>, key_to_bin(UId)},
     {<<"zone_bin">>, key_to_bin(Zone)}];
@@ -81,13 +78,12 @@ make_indices(Record) -> [
 riak_client() -> [{_,_,{_,C}}] = ets:lookup(config, "riak_client"), C.
 
 put(Records) when is_list(Records) -> lists:foreach(fun riak_put/1, Records);
-put(Record) -> store_riak:put([Record]).
+put(Record) -> riak_put(Record).
 
 riak_put(Record) ->
     Object = make_object(Record),
     Riak = riak_client(),
     Result = Riak:put(Object),
-    error_logger:info_msg("RIAK PUT RES ~p",[Result]),
     post_write_hooks(Record, Riak),
     Result.
 
@@ -95,12 +91,8 @@ put_if_none_match(Record) ->
     Object = make_object(Record),
     Riak = riak_client(),
     case Riak:put(Object, [if_none_match]) of
-        ok ->
-            post_write_hooks(Record, Riak),
-            ok;
-        Error ->
-            Error
-    end.
+        ok -> post_write_hooks(Record, Riak), ok;
+        Error -> Error end.
 
 update(Record, Object) ->
     NewObject = make_object(Record),