@@ -859,6 +859,138 @@ func (n *OpenBazaarNode) EstimateOrderTotal(data *repo.PurchaseData) (*big.Int,
859
859
return n .CalculateOrderTotal (contract )
860
860
}
861
861
862
+ // CheckoutBreakdown - returns order total and breakdown of charges
863
+ func (n * OpenBazaarNode ) CheckoutBreakdown (data * repo.PurchaseData ) (repo.CheckoutBreakdown , error ) {
864
+ var checkoutBreakdown repo.CheckoutBreakdown
865
+ emptyCheckoutBreakdown := repo.CheckoutBreakdown {}
866
+
867
+ cc , err := n .ReserveCurrencyConverter ()
868
+ if err != nil {
869
+ return emptyCheckoutBreakdown , fmt .Errorf ("preparing reserve currency converter: %s" , err .Error ())
870
+ }
871
+
872
+ contract , err := n .createContractWithOrder (data )
873
+ if err != nil {
874
+ return emptyCheckoutBreakdown , err
875
+ }
876
+ payment := new (pb.Order_Payment )
877
+ defn , err := n .LookupCurrency (data .PaymentCoin )
878
+ if err != nil {
879
+ return emptyCheckoutBreakdown , errors .New ("invalid payment coin" )
880
+ }
881
+ payment .AmountCurrency = & pb.CurrencyDefinition {
882
+ Code : defn .Code .String (),
883
+ Divisibility : uint32 (defn .Divisibility ),
884
+ }
885
+ contract .BuyerOrder .Payment = payment
886
+
887
+ // Get base price of item
888
+ v5Order , err := repo .ToV5Order (contract .BuyerOrder , n .LookupCurrency )
889
+ firstItem := v5Order .Items [0 ]
890
+
891
+ nrl , err := GetNormalizedListing (firstItem .ListingHash , contract )
892
+ if err != nil {
893
+ return emptyCheckoutBreakdown , err
894
+ }
895
+
896
+ totalSurcharge , err := GetTotalSurchargeAmount (nrl , firstItem .Options )
897
+ if err != nil {
898
+ return emptyCheckoutBreakdown , err
899
+ }
900
+
901
+ itemOriginAmt , err := GetOriginalAmount (nrl , firstItem )
902
+ if err != nil {
903
+ return emptyCheckoutBreakdown , err
904
+ }
905
+
906
+ // Calculate total items
907
+ totalQuantity := new (big.Int ).SetInt64 (0 )
908
+ for _ , i := range data .Items {
909
+ iCount , ok := new (big.Int ).SetString (i .Quantity , 10 )
910
+ if ! ok {
911
+ return emptyCheckoutBreakdown , err
912
+ }
913
+ totalQuantity = totalQuantity .Add (totalQuantity , iCount )
914
+ }
915
+
916
+ // Calculate total price for the order
917
+ totalPrice , err := n .CalculateOrderTotal (contract )
918
+ if err != nil {
919
+ return emptyCheckoutBreakdown , err
920
+ }
921
+
922
+ finalBasePrice , _ , err := itemOriginAmt .ConvertUsingProtobufDef (v5Order .Payment .AmountCurrency , cc )
923
+ if err != nil {
924
+ return emptyCheckoutBreakdown , err
925
+ }
926
+
927
+ // Coupon Codes Discount
928
+ listingCurDef , err := n .LookupCurrency (contract .VendorListings [0 ].Item .PriceCurrency .Code )
929
+ if err != nil {
930
+ return emptyCheckoutBreakdown , err
931
+ }
932
+ couponDiscount , err := GetTotalCouponCodeDiscount (nrl , firstItem .CouponCodes )
933
+ couponDiscount = couponDiscount .Mul (couponDiscount , new (big.Int ).SetInt64 (- 1 ))
934
+ couponCurrencyValue := repo .NewCurrencyValueFromBigInt (couponDiscount , listingCurDef )
935
+
936
+ finalCouponDiscount , _ , err := couponCurrencyValue .ConvertUsingProtobufDef (v5Order .Payment .AmountCurrency , cc )
937
+ if err != nil {
938
+ return emptyCheckoutBreakdown , err
939
+ }
940
+
941
+ optionCurrencyValue := repo .NewCurrencyValueFromBigInt (totalSurcharge , listingCurDef )
942
+
943
+ finalOptionSurcharge , _ , err := optionCurrencyValue .ConvertUsingProtobufDef (v5Order .Payment .AmountCurrency , cc )
944
+ if err != nil {
945
+ return emptyCheckoutBreakdown , err
946
+ }
947
+
948
+ // Shipping Costs
949
+ isPhysicalGood := false
950
+ var physicalGoods = make (map [string ]* repo.Listing )
951
+
952
+ if nrl .GetContractType () == pb .Listing_Metadata_PHYSICAL_GOOD .String () {
953
+ isPhysicalGood = true
954
+ physicalGoods [firstItem .ListingHash ] = nrl
955
+ }
956
+
957
+ shippingTotal := new (big.Int ).SetInt64 (0 )
958
+ if isPhysicalGood {
959
+
960
+ shippingTotal , err = n .calculateShippingTotalForListings (contract , physicalGoods )
961
+ if err != nil {
962
+ return emptyCheckoutBreakdown , err
963
+ }
964
+ }
965
+
966
+ // Taxes
967
+ taxesTotal := new (big.Int ).SetInt64 (0 )
968
+ for _ , tax := range nrl .GetProtobuf ().Taxes {
969
+ for _ , taxRegion := range tax .TaxRegions {
970
+ if contract .BuyerOrder .Shipping .Country == taxRegion {
971
+ factor := toHundredths (tax .Percentage )
972
+ taxes , _ := new (big.Float ).Mul (new (big.Float ).SetInt (itemOriginAmt .Amount ), factor ).Int (nil )
973
+ taxesTotal = new (big.Int ).Add (taxesTotal , taxes )
974
+ break
975
+ }
976
+ }
977
+ }
978
+
979
+ checkoutBreakdown .Tax = taxesTotal .String ()
980
+ checkoutBreakdown .ShippingPrice = shippingTotal .String ()
981
+ checkoutBreakdown .Coupon = finalCouponDiscount .Amount .String ()
982
+ checkoutBreakdown .OptionSurcharge = finalOptionSurcharge .Amount .String ()
983
+ checkoutBreakdown .BasePrice = finalBasePrice .Amount .String ()
984
+ checkoutBreakdown .PriceCurrency = repo.CheckoutCurrency {
985
+ Code : itemOriginAmt .Currency .Code .String (),
986
+ Divisibility : int (itemOriginAmt .Currency .Divisibility ),
987
+ }
988
+ checkoutBreakdown .TotalPrice = totalPrice .String ()
989
+ checkoutBreakdown .Quantity = totalQuantity .String ()
990
+
991
+ return checkoutBreakdown , nil
992
+ }
993
+
862
994
// CancelOfflineOrder - cancel order
863
995
func (n * OpenBazaarNode ) CancelOfflineOrder (contract * pb.RicardianContract , records []* wallet.TransactionRecord ) error {
864
996
v5Order , err := repo .ToV5Order (contract .BuyerOrder , nil )
@@ -952,92 +1084,44 @@ func (n *OpenBazaarNode) CalculateOrderTotal(contract *pb.RicardianContract) (*b
952
1084
var (
953
1085
total = big .NewInt (0 )
954
1086
physicalGoods = make (map [string ]* repo.Listing )
955
- toHundredths = func (f float32 ) * big.Float {
956
- return new (big.Float ).Mul (big .NewFloat (float64 (f )), big .NewFloat (0.01 ))
957
- }
958
- v5Order , err = repo .ToV5Order (contract .BuyerOrder , n .LookupCurrency )
1087
+ v5Order , err = repo .ToV5Order (contract .BuyerOrder , n .LookupCurrency )
959
1088
)
960
1089
if err != nil {
961
1090
return big .NewInt (0 ), fmt .Errorf ("normalizing buyer order: %s" , err .Error ())
962
1091
}
963
1092
964
1093
for _ , item := range v5Order .Items {
965
1094
var itemOriginAmt * repo.CurrencyValue
966
- l , err := ParseContractForListing (item .ListingHash , contract )
967
- if err != nil {
968
- return big .NewInt (0 ), fmt .Errorf ("listing not found in contract for item %s" , item .ListingHash )
969
- }
970
1095
971
- rl , err := repo . NewListingFromProtobuf ( l )
1096
+ nrl , err := GetNormalizedListing ( item . ListingHash , contract )
972
1097
if err != nil {
973
1098
return big .NewInt (0 ), err
974
1099
}
975
1100
976
- nrl , err := rl .Normalize ()
977
- if err != nil {
978
- return big .NewInt (0 ), fmt .Errorf ("normalize legacy listing: %s" , err .Error ())
979
- }
980
-
981
1101
// keep track of physical listings for shipping caluclation
982
1102
if nrl .GetContractType () == pb .Listing_Metadata_PHYSICAL_GOOD .String () {
983
1103
physicalGoods [item .ListingHash ] = nrl
984
1104
}
985
1105
986
1106
// calculate base amount
987
- if nrl .GetContractType () == pb .Listing_Metadata_CRYPTOCURRENCY .String () &&
988
- nrl .GetFormat () == pb .Listing_Metadata_MARKET_PRICE .String () {
989
- var originDef = repo .NewUnknownCryptoDefinition (nrl .GetCryptoCurrencyCode (), uint (nrl .GetCryptoDivisibility ()))
990
- itemOriginAmt = repo .NewCurrencyValueFromBigInt (GetOrderQuantity (nrl .GetProtobuf (), item ), originDef )
991
-
992
- if priceModifier := nrl .GetPriceModifier (); priceModifier != 0 {
993
- itemOriginAmt = itemOriginAmt .AddBigFloatProduct (toHundredths (priceModifier ))
994
- }
995
- } else {
996
- oAmt , err := nrl .GetPrice ()
997
- if err != nil {
998
- return big .NewInt (0 ), err
999
- }
1000
- itemOriginAmt = oAmt
1001
- }
1002
-
1003
- // apply surcharges
1004
- selectedSku , err := GetSelectedSku (nrl .GetProtobuf (), item .Options )
1107
+ itemOriginAmt , err = GetOriginalAmount (nrl , item )
1005
1108
if err != nil {
1006
1109
return big .NewInt (0 ), err
1007
1110
}
1008
- skus , err := nrl .GetSkus ()
1111
+
1112
+ // apply surcharges
1113
+ totalSurcharge , err := GetTotalSurchargeAmount (nrl , item .Options )
1009
1114
if err != nil {
1010
1115
return big .NewInt (0 ), err
1011
1116
}
1012
- for i , sku := range skus {
1013
- if selectedSku == i {
1014
- // surcharge may be positive or negative
1015
- surcharge , ok := new (big.Int ).SetString (sku .BigSurcharge , 10 )
1016
- if ok && surcharge .Cmp (big .NewInt (0 )) != 0 {
1017
- itemOriginAmt = itemOriginAmt .AddBigInt (surcharge )
1018
- }
1019
- break
1020
- }
1021
- }
1117
+ itemOriginAmt = itemOriginAmt .AddBigInt (totalSurcharge )
1022
1118
1023
1119
// apply coupon discounts
1024
- for _ , couponCode := range item .CouponCodes {
1025
- id , err := ipfs .EncodeMultihash ([]byte (couponCode ))
1026
- if err != nil {
1027
- return big .NewInt (0 ), err
1028
- }
1029
- for _ , vendorCoupon := range nrl .GetProtobuf ().Coupons {
1030
- if id .B58String () == vendorCoupon .GetHash () {
1031
- if disc , ok := new (big.Int ).SetString (vendorCoupon .GetBigPriceDiscount (), 10 ); ok && disc .Cmp (big .NewInt (0 )) > 0 {
1032
- // apply fixed discount
1033
- itemOriginAmt = itemOriginAmt .SubBigInt (disc )
1034
- } else if discountF := vendorCoupon .GetPercentDiscount (); discountF > 0 {
1035
- // apply percentage discount
1036
- itemOriginAmt = itemOriginAmt .AddBigFloatProduct (toHundredths (- discountF ))
1037
- }
1038
- }
1039
- }
1120
+ totalDiscount , err := GetTotalCouponCodeDiscount (nrl , item .CouponCodes )
1121
+ if err != nil {
1122
+ return big .NewInt (0 ), err
1040
1123
}
1124
+ itemOriginAmt = itemOriginAmt .AddBigInt (totalDiscount )
1041
1125
1042
1126
// apply taxes
1043
1127
for _ , tax := range nrl .GetProtobuf ().Taxes {
@@ -1083,6 +1167,98 @@ func (n *OpenBazaarNode) CalculateOrderTotal(contract *pb.RicardianContract) (*b
1083
1167
return total , nil
1084
1168
}
1085
1169
1170
+ func GetTotalCouponCodeDiscount (nrl * repo.Listing , couponCodes []string ) (* big.Int , error ) {
1171
+ totalCouponCodeDiscount := big .NewInt (0 )
1172
+
1173
+ for _ , couponCode := range couponCodes {
1174
+ id , err := ipfs .EncodeMultihash ([]byte (couponCode ))
1175
+ if err != nil {
1176
+ return big .NewInt (0 ), err
1177
+ }
1178
+ coupons := nrl .GetProtobuf ().Coupons
1179
+ for _ , vendorCoupon := range coupons {
1180
+ if id .B58String () == vendorCoupon .GetHash () {
1181
+ if disc , ok := new (big.Int ).SetString (vendorCoupon .GetBigPriceDiscount (), 10 ); ok && disc .Cmp (big .NewInt (0 )) > 0 {
1182
+ // apply fixed discount
1183
+ totalCouponCodeDiscount .Sub (totalCouponCodeDiscount , disc )
1184
+ } else if discountF := vendorCoupon .GetPercentDiscount (); discountF > 0 {
1185
+ // apply percentage discount
1186
+ disc , _ := toHundredths (- discountF ).Int (nil )
1187
+ totalCouponCodeDiscount .Add (totalCouponCodeDiscount , disc )
1188
+ }
1189
+ }
1190
+ }
1191
+ }
1192
+ return totalCouponCodeDiscount , nil
1193
+ }
1194
+
1195
+ func GetTotalSurchargeAmount (nrl * repo.Listing , options []* pb.Order_Item_Option ) (* big.Int , error ) {
1196
+ totalSurchargeAmount := big .NewInt (0 )
1197
+
1198
+ selectedSku , err := GetSelectedSku (nrl .GetProtobuf (), options )
1199
+ if err != nil {
1200
+ return big .NewInt (0 ), err
1201
+ }
1202
+ skus , err := nrl .GetSkus ()
1203
+ if err != nil {
1204
+ return big .NewInt (0 ), err
1205
+ }
1206
+ for i , sku := range skus {
1207
+ if selectedSku == i {
1208
+ // surcharge may be positive or negative
1209
+ surcharge , ok := new (big.Int ).SetString (sku .BigSurcharge , 10 )
1210
+ if ok && surcharge .Cmp (big .NewInt (0 )) != 0 {
1211
+ totalSurchargeAmount .Add (totalSurchargeAmount , surcharge )
1212
+ }
1213
+ break
1214
+ }
1215
+ }
1216
+ return totalSurchargeAmount , nil
1217
+ }
1218
+ func toHundredths (f float32 ) * big.Float {
1219
+ return new (big.Float ).Mul (big .NewFloat (float64 (f )), big .NewFloat (0.01 ))
1220
+ }
1221
+
1222
+ func GetNormalizedListing (listingHash string , contract * pb.RicardianContract ) (* repo.Listing , error ) {
1223
+ l , err := ParseContractForListing (listingHash , contract )
1224
+ if err != nil {
1225
+ return nil , fmt .Errorf ("listing not found in contract for item %s" , listingHash )
1226
+ }
1227
+
1228
+ rl , err := repo .NewListingFromProtobuf (l )
1229
+ if err != nil {
1230
+ return nil , err
1231
+ }
1232
+
1233
+ nrl , err := rl .Normalize ()
1234
+ if err != nil {
1235
+ return nil , fmt .Errorf ("normalize legacy listing: %s" , err .Error ())
1236
+ }
1237
+
1238
+ return nrl , nil
1239
+ }
1240
+
1241
+ func GetOriginalAmount (nrl * repo.Listing , item * pb.Order_Item ) (* repo.CurrencyValue , error ) {
1242
+ var itemOriginAmt * repo.CurrencyValue
1243
+
1244
+ if nrl .GetContractType () == pb .Listing_Metadata_CRYPTOCURRENCY .String () &&
1245
+ nrl .GetFormat () == pb .Listing_Metadata_MARKET_PRICE .String () {
1246
+ var originDef = repo .NewUnknownCryptoDefinition (nrl .GetCryptoCurrencyCode (), uint (nrl .GetCryptoDivisibility ()))
1247
+ itemOriginAmt = repo .NewCurrencyValueFromBigInt (GetOrderQuantity (nrl .GetProtobuf (), item ), originDef )
1248
+
1249
+ if priceModifier := nrl .GetPriceModifier (); priceModifier != 0 {
1250
+ itemOriginAmt = itemOriginAmt .AddBigFloatProduct (toHundredths (priceModifier ))
1251
+ }
1252
+ } else {
1253
+ oAmt , err := nrl .GetPrice ()
1254
+ if err != nil {
1255
+ return nil , err
1256
+ }
1257
+ itemOriginAmt = oAmt
1258
+ }
1259
+ return itemOriginAmt , nil
1260
+ }
1261
+
1086
1262
func (n * OpenBazaarNode ) calculateShippingTotalForListings (contract * pb.RicardianContract , listings map [string ]* repo.Listing ) (* big.Int , error ) {
1087
1263
type itemShipping struct {
1088
1264
primary * big.Int
0 commit comments