Permalink
Browse files

WHISTLE-1217: finish support for allowing requests and using services…

… plans for the current vendor only
  • Loading branch information...
1 parent b64b2b8 commit c130aa0954ab15616d7d4d4c6bb197c4bebd569a @k-anderson k-anderson committed Aug 15, 2012
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -72,9 +72,12 @@ new_subscription(PlanId, Customer) ->
%% Given a cutomer record find (if any) the default payment token
%% @end
%%--------------------------------------------------------------------
--spec default_payment_token/1 :: (#bt_customer{}) -> 'undefined' | ne_binary().
+-spec default_payment_token/1 :: (ne_binary() | #bt_customer{}) -> 'undefined' | ne_binary().
default_payment_token(#bt_customer{}=Customer) ->
- braintree_card:default_payment_token(get_cards(Customer)).
+ braintree_card:default_payment_token(get_cards(Customer));
+default_payment_token(CustomerId) ->
+ default_payment_token(find(CustomerId)).
+
%%--------------------------------------------------------------------
%% @public
@@ -464,53 +464,20 @@ record_to_xml(#bt_subscription{}=Subscription, ToString) ->
,{'add-ons', create_addon_changes(Subscription#bt_subscription.add_ons)}
,{'discounts', create_discount_changes(Subscription#bt_subscription.discounts)}
],
- Conditionals = [fun(#bt_subscription{do_not_inherit=true}, P) ->
- case proplists:get_value('options', P) of
- undefined ->
- [{'options', [{'do-not-inherit-add-ons-or-discounts', true}]}|P];
- Options ->
- [{'options', [{'do-not-inherit-add-ons-or-discounts', true}|Options]}
- |proplists:delete('options', P)
- ]
- end;
- (_, P) -> P
+ Conditionals = [fun(#bt_subscription{do_not_inherit=Value}, P) ->
+ update_options('do-not-inherit-add-ons-or-discounts', Value, P)
end
- ,fun(#bt_subscription{prorate_charges=true}, P) ->
- case proplists:get_value('options', P) of
- undefined ->
- [{'options', [{'prorate-charges', true}]}|P];
- Options ->
- [{'options', [{'prorate-charges', true}|Options]}
- |proplists:delete('options', P)
- ]
- end;
- (_, P) -> P
+ ,fun(#bt_subscription{prorate_charges=Value}, P) ->
+ update_options('prorate-charges', Value, P)
end
- ,fun(#bt_subscription{revert_on_prorate_fail=true}, P) ->
- case proplists:get_value('options', P) of
- undefined ->
- [{'options', [{'revert-subscription-on-proration-failure', true}]}|P];
- Options ->
- [{'options', [{'revert-subscription-on-proration-failure', true}|Options]}
- |proplists:delete('options', P)
- ]
- end;
- (_, P) -> P
+ ,fun(#bt_subscription{revert_on_prorate_fail=Value}, P) ->
+ update_options('revert-subscription-on-proration-failure', Value, P)
end
- ,fun(#bt_subscription{replace_add_ons=true}, P) ->
- case proplists:get_value('options', P) of
- undefined ->
- [{'options', [{'replace-all-add-ons-and-discounts', true}]}|P];
- Options ->
- [{'options', [{'replace-all-add-ons-and-discounts', true}|Options]}
- |proplists:delete('options', P)
- ]
- end;
- (_, P) -> P
+ ,fun(#bt_subscription{replace_add_ons=Value}, P) ->
+ update_options('replace-all-add-ons-and-discounts', Value, P)
end
- ,fun(#bt_subscription{start_immediately=true}, P) ->
- [{'options', [{'start-immediately', true}]}|P];
- (_, P) -> P
+ ,fun(#bt_subscription{start_immediately=Value}, P) ->
+ update_options('start-immediately', Value, P)
end
],
Props1 = lists:foldr(fun(F, P) -> F(Subscription, P) end, Props, Conditionals),
@@ -519,6 +486,23 @@ record_to_xml(#bt_subscription{}=Subscription, ToString) ->
false -> Props1
end.
+%%--------------------------------------------------------------------
+%% @private
+%% @doc
+%% Determine the necessary steps to change the add ons
+%% @end
+%%--------------------------------------------------------------------
+-spec update_options/3 :: (_, _, proplist()) -> proplist().
+update_options(Key, Value, Props) ->
+ case proplists:get_value('options', Props) of
+ undefined ->
+ [{'options', [{Key, Value}]}|Props];
+ Options ->
+ [{'options', [{Key, Value}|Options]}
+ |proplists:delete('options', Props)
+ ]
+ end.
+
%%--------------------------------------------------------------------
%% @private
%% @doc
View
@@ -2,6 +2,6 @@
{lib_dirs, ["."]}.
{sub_dirs, ["whistle-1.0.0", "whistle_amqp-1.0.0", "whistle_couch-1.0.0", "whistle_stats-1.0.0"
,"whistle_number_manager-1.0.0", "erlydtl-0.7.0", "kazoo_translator-1.0.0"
- ,"whistle_service-1.0.0"
+ ,"whistle_services-1.0.0"
]}.
{dialyzer_opts, [{warnings, [unmatched_returns, race_conditions, error_handling, underspecs]}]}.
@@ -3,7 +3,7 @@
"language": "javascript",
"views": {
"cascade_quantities": {
- "map": "function (doc) {if (doc.pvt_type != 'service' || doc.pvt_deleted || !doc.quantities) return;for (var key in doc.quantities) {var obj = doc.quantities[key];for (var prop in obj) {if (obj[prop] < 1) continue;if (doc.billing_id == doc._id) {emit([doc._id, key, prop], obj[prop]);} else {for (var idx in doc.pvt_tree) {emit([doc.pvt_tree[idx] || doc._id, key, prop], obj[prop]);}}}}}",
+ "map": "function (doc) {if (doc.pvt_type != 'service' || doc.pvt_deleted || !doc.quantities || doc.billing_id == doc._id) return;for (var key in doc.quantities) {var obj = doc.quantities[key];for (var prop in obj) {if (obj[prop] < 1) continue;for (var idx in doc.pvt_tree) {emit([doc.pvt_tree[idx] || doc._id, key, prop], obj[prop]);}}}}",
"reduce": "_sum"
},
"dirty": {
@@ -1,5 +1,6 @@
{
"_id": "968dc36503bcb05f798d9530016f311f",
+ "_rev": "45-e0a49ed498cd6884dc8262b98cc6188d",
"name": "Direct Web Signups, No Support",
"pvt_type": "service_plan",
"description": "",
@@ -0,0 +1,90 @@
+{
+ "_id": "e748394e1d1b85eeb3f6bec0e0a6d404",
+ "_rev": "8-53977ba6d28c5f5369cc3099d02a9949",
+ "name": "Full Service",
+ "description": "",
+ "plan": {
+ "phone_numbers": {
+ "did_us": {
+ "name": "US DID",
+ "rate": 1,
+ "discount": {
+ "cumulative": {
+ "rates": {
+ "5": 0,
+ "10": 0.25,
+ "20": 0.5
+ }
+ }
+ },
+ "cascade": true
+ },
+ "tollfree_us": {
+ "name": "US Tollfree",
+ "rate": 5,
+ "cascade": true
+ }
+ },
+ "number_services": {
+ },
+ "limits": {
+ "twoway_trunks": {
+ "name": "Two-Way Trunk",
+ "rate": 19.99
+ },
+ "inbound_trunks": {
+ "name": "Inbound Trunk",
+ "rate": 10.99
+ }
+ },
+ "devices": {
+ "_all": {
+ "name": "SIP Device",
+ "as": "sip_devices",
+ "rates": {
+ "5": 0,
+ "20": 24.95,
+ "50": 49.95,
+ "100": 149.95
+ },
+ "cascade": true
+ }
+ }
+ },
+ "bookkeepers": {
+ "braintree": {
+ "phone_numbers": {
+ "did_us": {
+ "plan": "SIP_Services",
+ "addon": "did_us",
+ "discounts": {
+ "cumulative": "discount_did_us"
+ }
+ },
+ "tollfree_us": {
+ "plan": "SIP_Services",
+ "addon": "tollfree_us"
+ }
+ },
+ "number_services": {
+ },
+ "limits": {
+ "twoway_trunks": {
+ "plan": "SIP_Services",
+ "addon": "twoway_trunk"
+ },
+ "inbound_trunks": {
+ "plan": "SIP_Services",
+ "addon": "inbound_trunk"
+ }
+ },
+ "devices": {
+ "sip_devices": {
+ "plan": "SIP_Services",
+ "addon": "sip_device"
+ }
+ }
+ }
+ },
+ "pvt_type": "service_plan"
+}
@@ -10,6 +10,7 @@
-include_lib("whistle_services/src/whistle_services.hrl").
-export([sync/2]).
+-export([is_good_standing/1]).
-record(wh_service_update, {bt_subscription
,plan_id
@@ -19,6 +20,23 @@
,account_id
,bt_customer
}).
+
+%%--------------------------------------------------------------------
+%% @public
+%% @doc
+%%
+%% @end
+%%--------------------------------------------------------------------
+is_good_standing(AccountId) ->
+ try braintree_customer:default_payment_token(AccountId) of
+ _ ->
+ lager:debug("braintree customer ~s is believed to be in good standing", [AccountId]),
+ true
+ catch
+ throw:_R ->
+ lager:debug("braintree customer ~s is not in good standing: ~p", [AccountId, _R]),
+ false
+ end.
%%--------------------------------------------------------------------
%% @public
@@ -27,8 +45,12 @@
%% @end
%%--------------------------------------------------------------------
sync(Items, AccountId) ->
- Customer = fetch_or_create_customer(AccountId),
- sync(wh_service_items:to_list(Items), AccountId, #wh_service_updates{bt_customer=Customer}).
+ ItemList = wh_service_items:to_list(Items),
+ case fetch_bt_customer(AccountId, ItemList =/= []) of
+ undefined -> ok;
+ Customer ->
+ sync(ItemList, AccountId, #wh_service_updates{bt_customer=Customer})
+ end.
sync([], _AccountId, #wh_service_updates{bt_subscriptions=Subscriptions}) ->
_ = [braintree_subscription:update(Subscription)
@@ -103,15 +125,15 @@ handle_cumulative_discounts(ServiceItem, Subscription) ->
%%
%% @end
%%--------------------------------------------------------------------
--spec fetch_or_create_customer/1 :: (ne_binary()) -> braintree_customer:customer().
-fetch_or_create_customer(AccountId) ->
+-spec fetch_bt_customer/2 :: (ne_binary(), boolean()) -> 'undefined' | braintree_customer:customer().
+fetch_bt_customer(AccountId, NewItems) ->
lager:debug("requesting braintree customer ~s", [AccountId]),
try braintree_customer:find(AccountId) of
Customer -> Customer
catch
- throw:{not_found, _} ->
- lager:debug("creating new braintree customer ~s", [AccountId]),
- braintree_customer:create(AccountId)
+ throw:{not_found, Error} when NewItems ->
+ throw({no_payment_token, wh_json:from_list([{<<"no_payment_token">>, Error}])});
+ throw:{not_found, _} -> undefined
end.
%%--------------------------------------------------------------------
@@ -190,12 +190,20 @@ sync_services(AccountId, ServiceJObj) ->
case wh_service_plans:create_items(ServiceJObj) of
{error, no_plans} ->
lager:debug("no services plans found", []),
- finalize_sync(AccountId, ServiceJObj);
+ _ = mark_clean_and_status(<<"good_standing">>, ServiceJObj),
+ maybe_sync_reseller(AccountId, ServiceJObj);
{ok, ServiceItems} ->
%% TODO: support other bookkeepers...
try wh_bookkeeper_braintree:sync(ServiceItems, AccountId) of
- _ -> finalize_sync(AccountId, ServiceJObj)
+ _ ->
+ _ = mark_clean_and_status(<<"good_standing">>, ServiceJObj),
+ lager:debug("synchronization with bookkeeper complete", []),
+ maybe_sync_reseller(AccountId, ServiceJObj)
catch
+ throw:{Reason, _}=_R ->
+ lager:debug("bookkeeper error: ~p", [_R]),
+ _ = mark_clean_and_status(wh_util:to_binary(Reason), ServiceJObj),
+ maybe_sync_reseller(AccountId, ServiceJObj);
_E:R ->
%% TODO: certain errors (such as no CC or expired, ect) should
%% move the account of good standing...
@@ -204,10 +212,8 @@ sync_services(AccountId, ServiceJObj) ->
end
end.
--spec finalize_sync/2 :: (ne_binary(), wh_json:json_object()) -> wh_std_return().
-finalize_sync(AccountId, ServiceJObj) ->
- _ = mark_clean(ServiceJObj),
- lager:debug("synchronization with bookkeeper complete", []),
+-spec maybe_sync_reseller/2 :: (ne_binary(), wh_json:json_object()) -> wh_std_return().
+maybe_sync_reseller(AccountId, ServiceJObj) ->
case wh_json:get_ne_value(<<"pvt_reseller_id">>, ServiceJObj, AccountId) of
AccountId -> {ok, ServiceJObj};
ResellerId ->
@@ -242,6 +248,19 @@ mark_clean(AccountId) when is_binary(AccountId) ->
mark_clean(JObj) ->
couch_mgr:save_doc(?WH_SERVICES_DB, wh_json:set_value(<<"pvt_dirty">>, false, JObj)).
+
+-spec mark_clean_and_status/2 :: (ne_binary(), ne_binary() | wh_json:json_object()) -> wh_std_return().
+mark_clean_and_status(Status, AccountId) when is_binary(AccountId) ->
+ case couch_mgr:open_doc(?WH_SERVICES_DB, AccountId) of
+ {error, _}=E -> E;
+ {ok, JObj} -> mark_clean_and_status(Status, JObj)
+ end;
+mark_clean_and_status(Status, JObj) ->
+ lager:debug("marking services clean with status ~s", [Status]),
+ couch_mgr:save_doc(?WH_SERVICES_DB, wh_json:set_values([{<<"pvt_dirty">>, false}
+ ,{<<"pvt_status">>, Status}
+ ], JObj)).
+
-spec maybe_update_billing_id/3 :: (ne_binary(), ne_binary(), wh_json:json_object()) -> wh_std_return().
maybe_update_billing_id(BillingId, AccountId, ServiceJObj) ->
case couch_mgr:open_doc(?WH_ACCOUNTS_DB, BillingId) of
Oops, something went wrong.

0 comments on commit c130aa0

Please sign in to comment.