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

Commit 0e44b51

Browse files
authored
Merge pull request #755 from OpenBazaar/shipping
Refactor shipping costs
2 parents d65a3f5 + 52026df commit 0e44b51

File tree

8 files changed

+268
-683
lines changed

8 files changed

+268
-683
lines changed

api/jsonapi.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,6 +1406,13 @@ func (i *jsonAPIHandler) GETListing(w http.ResponseWriter, r *http.Request) {
14061406
}
14071407
}
14081408
}
1409+
if sl.Listing.Metadata != nil && sl.Listing.Metadata.Version == 1 {
1410+
for _, so := range sl.Listing.ShippingOptions {
1411+
for _, ser := range so.Services {
1412+
ser.AdditionalItemPrice = ser.Price
1413+
}
1414+
}
1415+
}
14091416

14101417
out, err := m.MarshalToString(sl)
14111418
if err != nil {

api/jsonapi_data_test.go

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -425,22 +425,7 @@ const listingJSON = `{
425425
"price": 150000000,
426426
"estimatedDelivery": "2-3 days"
427427
}
428-
],
429-
"shippingRules": {
430-
"ruleType": "QUANTITY_DISCOUNT",
431-
"rules": [
432-
{
433-
"price": 10000000,
434-
"minRange": 5,
435-
"maxRange": 10
436-
},
437-
{
438-
"price": 200000,
439-
"minRange": 11,
440-
"maxRange": 20
441-
}
442-
]
443-
}
428+
]
444429
}
445430
],
446431
"taxes": [
@@ -626,22 +611,7 @@ const listingUpdateJSON = `{
626611
"price": 150000000,
627612
"estimatedDelivery": "2-3 days"
628613
}
629-
],
630-
"shippingRules": {
631-
"ruleType": "QUANTITY_DISCOUNT",
632-
"rules": [
633-
{
634-
"price": 10000000,
635-
"minRange": 5,
636-
"maxRange": 10
637-
},
638-
{
639-
"price": 200000,
640-
"minRange": 11,
641-
"maxRange": 20
642-
}
643-
]
644-
}
614+
]
645615
}
646616
],
647617
"taxes": [

core/listings.go

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
)
2727

2828
const (
29-
ListingVersion = 1
29+
ListingVersion = 2
3030
TitleMaxCharacters = 140
3131
ShortDescriptionLength = 160
3232
DescriptionMaxCharacters = 50000
@@ -948,41 +948,6 @@ func validateListing(listing *pb.Listing, testnet bool) (err error) {
948948
if len(shippingOption.Regions) > MaxCountryCodes {
949949
return fmt.Errorf("Number of shipping regions is greater than the max of %d", MaxCountryCodes)
950950
}
951-
if shippingOption.ShippingRules != nil {
952-
if len(shippingOption.ShippingRules.Rules) == 0 {
953-
return errors.New("At least on rule must be specified if ShippingRules is selected")
954-
}
955-
if len(shippingOption.ShippingRules.Rules) > MaxListItems {
956-
return fmt.Errorf("Number of shipping rules is greater than the max of %d", MaxListItems)
957-
}
958-
if shippingOption.ShippingRules.RuleType > pb.Listing_ShippingOption_ShippingRules_COMBINED_SHIPPING_SUBTRACT {
959-
return errors.New("Unknown shipping rule")
960-
}
961-
if shippingOption.ShippingRules.RuleType == pb.Listing_ShippingOption_ShippingRules_FLAT_FEE_WEIGHT_RANGE && listing.Item.Grams == 0 {
962-
return errors.New("Item weight must be specified when using FLAT_FEE_WEIGHT_RANGE shipping rule")
963-
}
964-
if (shippingOption.ShippingRules.RuleType == pb.Listing_ShippingOption_ShippingRules_COMBINED_SHIPPING_ADD || shippingOption.ShippingRules.RuleType == pb.Listing_ShippingOption_ShippingRules_COMBINED_SHIPPING_SUBTRACT) && len(shippingOption.ShippingRules.Rules) > 1 {
965-
return errors.New("Selected shipping rule type can only have a maximum of one rule")
966-
}
967-
if len(shippingOption.ShippingRules.Rules) > MaxListItems {
968-
return fmt.Errorf("Shipping rules exceeds max of %d", MaxListItems)
969-
}
970-
for i, rule := range shippingOption.ShippingRules.Rules {
971-
if (shippingOption.ShippingRules.RuleType == pb.Listing_ShippingOption_ShippingRules_FLAT_FEE_QUANTITY_RANGE ||
972-
shippingOption.ShippingRules.RuleType == pb.Listing_ShippingOption_ShippingRules_FLAT_FEE_WEIGHT_RANGE ||
973-
shippingOption.ShippingRules.RuleType == pb.Listing_ShippingOption_ShippingRules_QUANTITY_DISCOUNT) && rule.MaxRange <= rule.MinRange {
974-
return errors.New("Shipping rule max range cannot be less than or equal to the min range")
975-
}
976-
for x, checkRule := range shippingOption.ShippingRules.Rules {
977-
if x == i {
978-
continue
979-
}
980-
if (rule.MinRange >= checkRule.MinRange && rule.MinRange <= checkRule.MaxRange) || (rule.MaxRange <= checkRule.MaxRange && rule.MaxRange >= checkRule.MinRange) {
981-
return errors.New("Shipping rule ranges must not overlap")
982-
}
983-
}
984-
}
985-
}
986951
if len(shippingOption.Services) == 0 && shippingOption.Type != pb.Listing_ShippingOption_LOCAL_PICKUP {
987952
return errors.New("At least one service must be specified for a shipping option when not local pickup")
988953
}

core/order.go

Lines changed: 45 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -774,21 +774,21 @@ func (n *OpenBazaarNode) CalculateOrderTotal(contract *pb.RicardianContract) (ui
774774
}
775775

776776
// Add in shipping costs
777-
type combinedShipping struct {
778-
quantity int
779-
price uint64
780-
add bool
781-
modifier uint64
777+
type itemShipping struct {
778+
primary uint64
779+
secondary uint64
780+
quantity uint32
781+
shippingTaxPercentage float32
782+
version uint32
782783
}
783-
var combinedOptions []combinedShipping
784+
var is []itemShipping
784785

785-
var shippingTotal uint64
786+
// First loop through to validate and filter out non-physical items
786787
for _, item := range contract.BuyerOrder.Items {
787788
listing, ok := physicalGoods[item.ListingHash]
788789
if !ok { // Not physical good no need to calculate shipping
789790
continue
790791
}
791-
var itemShipping uint64
792792
// Check selected option exists
793793
shippingOptions := make(map[string]*pb.Listing_ShippingOption)
794794
for _, so := range listing.ShippingOptions {
@@ -827,11 +827,17 @@ func (n *OpenBazaarNode) CalculateOrderTotal(contract *pb.RicardianContract) (ui
827827
if err != nil {
828828
return 0, err
829829
}
830-
shippingPrice := uint64(item.Quantity) * shippingSatoshi
831-
itemShipping += shippingPrice
832-
shippingTaxPercentage := float32(0)
830+
831+
var secondarySatoshi uint64
832+
if service.AdditionalItemPrice > 0 {
833+
secondarySatoshi, err = n.getPriceInSatoshi(listing.Metadata.PricingCurrency, service.AdditionalItemPrice)
834+
if err != nil {
835+
return 0, err
836+
}
837+
}
833838

834839
// Calculate tax percentage
840+
var shippingTaxPercentage float32
835841
for _, tax := range listing.Taxes {
836842
regions := make(map[pb.CountryCode]bool)
837843
for _, taxRegion := range tax.TaxRegions {
@@ -843,98 +849,41 @@ func (n *OpenBazaarNode) CalculateOrderTotal(contract *pb.RicardianContract) (ui
843849
}
844850
}
845851

846-
// Apply shipping rules
847-
if option.ShippingRules != nil {
848-
for _, rule := range option.ShippingRules.Rules {
849-
switch option.ShippingRules.RuleType {
850-
case pb.Listing_ShippingOption_ShippingRules_QUANTITY_DISCOUNT:
851-
if item.Quantity >= rule.MinRange && item.Quantity <= rule.MaxRange {
852-
rulePrice, err := n.getPriceInSatoshi(listing.Metadata.PricingCurrency, rule.Price)
853-
if err != nil {
854-
return 0, err
855-
}
856-
itemShipping -= rulePrice
857-
}
858-
case pb.Listing_ShippingOption_ShippingRules_FLAT_FEE_QUANTITY_RANGE:
859-
if item.Quantity >= rule.MinRange && item.Quantity <= rule.MaxRange {
860-
itemShipping -= shippingPrice
861-
rulePrice, err := n.getPriceInSatoshi(listing.Metadata.PricingCurrency, rule.Price)
862-
if err != nil {
863-
return 0, err
864-
}
865-
itemShipping += rulePrice
866-
}
867-
case pb.Listing_ShippingOption_ShippingRules_FLAT_FEE_WEIGHT_RANGE:
868-
weight := listing.Item.Grams * float32(item.Quantity)
869-
if uint32(weight) >= rule.MinRange && uint32(weight) <= rule.MaxRange {
870-
itemShipping -= shippingPrice
871-
rulePrice, err := n.getPriceInSatoshi(listing.Metadata.PricingCurrency, rule.Price)
872-
if err != nil {
873-
return 0, err
874-
}
875-
itemShipping += rulePrice
876-
}
877-
case pb.Listing_ShippingOption_ShippingRules_COMBINED_SHIPPING_ADD:
878-
itemShipping -= shippingPrice
879-
rulePrice, err := n.getPriceInSatoshi(listing.Metadata.PricingCurrency, rule.Price)
880-
rulePrice += uint64(float32(rulePrice) * shippingTaxPercentage)
881-
shippingSatoshi += uint64(float32(shippingSatoshi) * shippingTaxPercentage)
882-
if err != nil {
883-
return 0, err
884-
}
885-
cs := combinedShipping{
886-
quantity: int(item.Quantity),
887-
price: shippingSatoshi,
888-
add: true,
889-
modifier: rulePrice,
890-
}
891-
combinedOptions = append(combinedOptions, cs)
892-
893-
case pb.Listing_ShippingOption_ShippingRules_COMBINED_SHIPPING_SUBTRACT:
894-
itemShipping -= shippingPrice
895-
rulePrice, err := n.getPriceInSatoshi(listing.Metadata.PricingCurrency, rule.Price)
896-
rulePrice += uint64(float32(rulePrice) * shippingTaxPercentage)
897-
shippingSatoshi += uint64(float32(shippingSatoshi) * shippingTaxPercentage)
898-
if err != nil {
899-
return 0, err
900-
}
901-
cs := combinedShipping{
902-
quantity: int(item.Quantity),
903-
price: shippingSatoshi,
904-
add: false,
905-
modifier: rulePrice,
906-
}
907-
combinedOptions = append(combinedOptions, cs)
908-
}
909-
}
910-
}
911-
// Apply tax
912-
itemShipping += uint64(float32(itemShipping) * shippingTaxPercentage)
913-
shippingTotal += itemShipping
852+
is = append(is, itemShipping{
853+
primary: shippingSatoshi,
854+
secondary: secondarySatoshi,
855+
quantity: item.Quantity,
856+
shippingTaxPercentage: shippingTaxPercentage,
857+
version: listing.Metadata.Version,
858+
})
914859
}
915860

916-
// Process combined shipping rules
917-
if len(combinedOptions) > 0 {
918-
lowestPrice := int64(-1)
919-
for _, v := range combinedOptions {
920-
if int64(v.price) < lowestPrice || lowestPrice == -1 {
921-
lowestPrice = int64(v.price)
861+
var shippingTotal uint64
862+
if len(is) == 1 {
863+
shippingTotal = (is[0].primary * uint64(((1+is[0].shippingTaxPercentage)*100)+.5) / 100)
864+
if is[0].quantity > 1 {
865+
if is[0].version == 1 {
866+
shippingTotal += (is[0].primary * uint64(((1+is[0].shippingTaxPercentage)*100)+.5) / 100) * uint64((is[0].quantity - 1))
867+
} else if is[0].version == 2 {
868+
shippingTotal += (is[0].secondary * uint64(((1+is[0].shippingTaxPercentage)*100)+.5) / 100) * uint64((is[0].quantity - 1))
869+
} else {
870+
return 0, errors.New("Unknown listing version")
922871
}
923872
}
924-
shippingTotal += uint64(lowestPrice)
925-
for _, o := range combinedOptions {
926-
modifier := o.modifier
927-
modifier *= (uint64(o.quantity) - 1)
928-
if o.add {
929-
shippingTotal += modifier
930-
} else {
931-
shippingTotal -= modifier
873+
} else if len(is) > 1 {
874+
var highest uint64
875+
var i int
876+
for x, s := range is {
877+
if s.primary > highest {
878+
highest = s.primary
879+
i = x
932880
}
881+
shippingTotal += (s.secondary * uint64(((1+s.shippingTaxPercentage)*100)+.5) / 100) * uint64(s.quantity)
933882
}
883+
shippingTotal -= (is[i].primary * uint64(((1+is[i].shippingTaxPercentage)*100)+.5) / 100)
884+
shippingTotal += (is[i].secondary * uint64(((1+is[i].shippingTaxPercentage)*100)+.5) / 100)
934885
}
935-
936-
total += shippingTotal
937-
return total, nil
886+
return total + shippingTotal, nil
938887
}
939888

940889
func (n *OpenBazaarNode) getPriceInSatoshi(currencyCode string, amount uint64) (uint64, error) {

0 commit comments

Comments
 (0)