Skip to content
This repository was archived by the owner on Mar 28, 2023. It is now read-only.

Commit 26147f8

Browse files
committed
Refactor order.go for Checkout Breakdown
1 parent 26fa546 commit 26147f8

File tree

1 file changed

+235
-59
lines changed

1 file changed

+235
-59
lines changed

core/order.go

Lines changed: 235 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,138 @@ func (n *OpenBazaarNode) EstimateOrderTotal(data *repo.PurchaseData) (*big.Int,
859859
return n.CalculateOrderTotal(contract)
860860
}
861861

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+
862994
// CancelOfflineOrder - cancel order
863995
func (n *OpenBazaarNode) CancelOfflineOrder(contract *pb.RicardianContract, records []*wallet.TransactionRecord) error {
864996
v5Order, err := repo.ToV5Order(contract.BuyerOrder, nil)
@@ -952,92 +1084,44 @@ func (n *OpenBazaarNode) CalculateOrderTotal(contract *pb.RicardianContract) (*b
9521084
var (
9531085
total = big.NewInt(0)
9541086
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)
9591088
)
9601089
if err != nil {
9611090
return big.NewInt(0), fmt.Errorf("normalizing buyer order: %s", err.Error())
9621091
}
9631092

9641093
for _, item := range v5Order.Items {
9651094
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-
}
9701095

971-
rl, err := repo.NewListingFromProtobuf(l)
1096+
nrl, err := GetNormalizedListing(item.ListingHash, contract)
9721097
if err != nil {
9731098
return big.NewInt(0), err
9741099
}
9751100

976-
nrl, err := rl.Normalize()
977-
if err != nil {
978-
return big.NewInt(0), fmt.Errorf("normalize legacy listing: %s", err.Error())
979-
}
980-
9811101
// keep track of physical listings for shipping caluclation
9821102
if nrl.GetContractType() == pb.Listing_Metadata_PHYSICAL_GOOD.String() {
9831103
physicalGoods[item.ListingHash] = nrl
9841104
}
9851105

9861106
// 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)
10051108
if err != nil {
10061109
return big.NewInt(0), err
10071110
}
1008-
skus, err := nrl.GetSkus()
1111+
1112+
// apply surcharges
1113+
totalSurcharge, err := GetTotalSurchargeAmount(nrl, item.Options)
10091114
if err != nil {
10101115
return big.NewInt(0), err
10111116
}
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)
10221118

10231119
// 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
10401123
}
1124+
itemOriginAmt = itemOriginAmt.AddBigInt(totalDiscount)
10411125

10421126
// apply taxes
10431127
for _, tax := range nrl.GetProtobuf().Taxes {
@@ -1083,6 +1167,98 @@ func (n *OpenBazaarNode) CalculateOrderTotal(contract *pb.RicardianContract) (*b
10831167
return total, nil
10841168
}
10851169

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+
10861262
func (n *OpenBazaarNode) calculateShippingTotalForListings(contract *pb.RicardianContract, listings map[string]*repo.Listing) (*big.Int, error) {
10871263
type itemShipping struct {
10881264
primary *big.Int

0 commit comments

Comments
 (0)