Skip to content

Commit

Permalink
Metadata enums, easier price updates (#675)
Browse files Browse the repository at this point in the history
* Adding metadata keys enum

* Supporting block customization when duplicating price

* Allow enum to be passed directly when generating metadata keys

* Missing price fields

* Fix block naming to be consistent

* Todo docs update

* Fixing price typing issue

* Fix missing metadata check
  • Loading branch information
mbianco-stripe authored Aug 17, 2022
1 parent 0486520 commit e0d8d42
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 30 deletions.
1 change: 1 addition & 0 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- Prices which are not created directly from pricebooks (i.e. from order lines, or duplicated because of the one-price-per-subscription) are archived (active = false) after they are used
- Prices which are duplicated because of the one-price-per-subscription) have a special metadata key (salesforce_duplicate = true)
- Some good test clock docs https://groups.google.com/a/stripe.com/g/cloudflare/c/VjNW1Q4KD-0
- `stripe_proration` metadata key on proration-generated prices, also contains duplicate key, and auto archive

Next:

Expand Down
9 changes: 9 additions & 0 deletions lib/stripe-force/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,14 @@ class FeatureFlags < T::Enum
end
end

class MetadataKeys < T::Enum
enums do
DUPLICATE_PRICE = new('duplicate')
PRORATION_PRICE = new('proration')
AUTO_ARCHIVE_PRICE = new('auto_archive')
ORIGINAL_PRICE_ID = new('original_stripe_price_id')
end
end

end
end
21 changes: 17 additions & 4 deletions lib/stripe-force/translate/order/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,14 @@ def self.transform_payment_terms_to_days_until_due(raw_days_until_due)
end
end

sig { params(user: StripeForce::User, original_stripe_price: Stripe::Price).returns(Stripe::Price) }
def self.duplicate_stripe_price(user, original_stripe_price)
sig do
params(
user: StripeForce::User,
original_stripe_price: Stripe::Price,
block: T.nilable(T.proc.params(arg0: Stripe::Price).void)
).returns(Stripe::Price)
end
def self.duplicate_stripe_price(user, original_stripe_price, &block)
stripe_price_params = original_stripe_price.to_hash
stripe_price_params.delete(:id)

Expand Down Expand Up @@ -90,10 +96,17 @@ def self.duplicate_stripe_price(user, original_stripe_price)
end

# indicate that this price was created as a duplicate for avoid Stripe API errors
stripe_price.metadata[StripeForce::Utilities::Metadata.metadata_key(user, "duplicate")] = true
stripe_price.metadata[StripeForce::Utilities::Metadata.metadata_key(user, MetadataKeys::DUPLICATE_PRICE)] = true

# indicates that this price will be auto-archived after created
stripe_price.metadata[StripeForce::Utilities::Metadata.metadata_key(user, "auto_archive")] = true
stripe_price.metadata[StripeForce::Utilities::Metadata.metadata_key(user, MetadataKeys::AUTO_ARCHIVE_PRICE)] = true

# links this price to the original price which this was generated from
stripe_price.metadata[StripeForce::Utilities::Metadata.metadata_key(user, MetadataKeys::ORIGINAL_PRICE_ID)] = original_stripe_price.id

if block_given?
yield(stripe_price)
end

Stripe::Price.create(stripe_price.to_hash, user.stripe_credentials)
end
Expand Down
48 changes: 25 additions & 23 deletions lib/stripe-force/translate/price.rb
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ def generate_price_params_from_sf_object(sf_object, sf_product)

# although we are passing the amount as a decimal, the decimal amount still represents cents
# to_s is used here to (a) satisfy typing requirements and (b) ensure BigDecimal can parse the float properly
stripe_price.unit_amount_decimal = normalize_float_amount_for_stripe(stripe_price.unit_amount_decimal.to_s, @user, as_decimal: true)
stripe_price.unit_amount_decimal = T.cast(normalize_float_amount_for_stripe(stripe_price.unit_amount_decimal.to_s, @user, as_decimal: true), BigDecimal)

# TODO validate billing frequency and subscription term

Expand Down Expand Up @@ -397,34 +397,36 @@ def generate_price_params_from_sf_object(sf_object, sf_product)
end

# we should only transform licensed prices that are not customized
if is_recurring_item && !is_tiered_price && sf_object.sobject_type == SF_ORDER_ITEM
if !PriceHelpers.using_custom_order_line_price_field?(@user)
log.info 'custom price not used, adjusting unit_amount_decimal', sf_order_item_id: sf_object['Id']
if is_recurring_item &&
!is_tiered_price &&
sf_object.sobject_type == SF_ORDER_ITEM &&
!PriceHelpers.using_custom_order_line_price_field?(@user)

billing_frequency = StripeForce::Utilities::StripeUtil.billing_frequency_of_price_in_months(stripe_price)
log.info 'custom price not used, adjusting unit_amount_decimal', sf_order_item_id: sf_object.Id

# TODO this is a hack and we should really extract this through the extract params
quote_subscription_term_path = 'Order.SBQQ__Quote__c.SBQQ__SubscriptionTerm__c'
quote_subscription_term = mapper.extract_key_path_for_record(sf_object, quote_subscription_term_path)
billing_frequency = StripeForce::Utilities::StripeUtil.billing_frequency_of_price_in_months(stripe_price)

if quote_subscription_term.nil?
raise Integrations::Errors::MissingRequiredFields.new(
salesforce_object: sf_object,
missing_salesforce_fields: [quote_subscription_term_path]
)
end
# TODO this is a hack and we should really extract this through the extract params
quote_subscription_term_path = 'Order.SBQQ__Quote__c.SBQQ__SubscriptionTerm__c'
quote_subscription_term = mapper.extract_key_path_for_record(sf_object, quote_subscription_term_path)

# it's looking like these values are never really aligned and we should ignore the line item
if sf_object['SBQQ__SubscriptionTerm__c'] == quote_subscription_term
report_edge_case("subscription term on quote matches line item")
end

# TODO need to stop hardcoding this value!
# NOTE in most cases, this value should be the same as `sf_object['SBQQ__ProrateMultiplier__c']` if the user has product-level subscription term enabled
price_multiplier = determine_subscription_term_multiplier_for_billing_frequency(quote_subscription_term, billing_frequency)
if quote_subscription_term.nil?
raise Integrations::Errors::MissingRequiredFields.new(
salesforce_object: sf_object,
missing_salesforce_fields: [quote_subscription_term_path]
)
end

stripe_price.unit_amount_decimal = stripe_price.unit_amount_decimal / price_multiplier
# it's looking like these values are never really aligned and we should ignore the line item
if sf_object['SBQQ__SubscriptionTerm__c'] == quote_subscription_term
report_edge_case("subscription term on quote matches line item")
end

# TODO need to stop hardcoding this value!
# NOTE in most cases, this value should be the same as `sf_object['SBQQ__ProrateMultiplier__c']` if the user has product-level subscription term enabled
price_multiplier = determine_subscription_term_multiplier_for_billing_frequency(quote_subscription_term, billing_frequency)

stripe_price.unit_amount_decimal = T.cast(stripe_price.unit_amount_decimal, BigDecimal) / price_multiplier
end

stripe_price
Expand Down
4 changes: 2 additions & 2 deletions lib/stripe-force/translate/translate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ def translate(sf_object)
end
end

sig { params(primary: T.nilable(Restforce::SObject), secondary: T.nilable(Restforce::SObject), blk: T.proc.returns(T.untyped)).returns(T.untyped) }
def catch_errors_with_salesforce_context(primary: nil, secondary: nil, &blk)
sig { params(primary: T.nilable(Restforce::SObject), secondary: T.nilable(Restforce::SObject), block: T.proc.returns(T.untyped)).returns(T.untyped) }
def catch_errors_with_salesforce_context(primary: nil, secondary: nil, &block)
if primary && @origin_salesforce_object
raise Integrations::Errors::ImpossibleInternalError.new("origin object already set, exiting")
end
Expand Down
6 changes: 5 additions & 1 deletion lib/stripe-force/translate/utilities/metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,12 @@ def metadata_key(key)
"#{@user.metadata_prefix}#{key}"
end

sig { params(user: StripeForce::User, key: String).returns(String) }
sig { params(user: StripeForce::User, key: T.any(T::Enum, String)).returns(String) }
def self.metadata_key(user, key)
if key.is_a?(T::Enum)
key = key.serialize
end

"#{user.metadata_prefix}#{key}"
end
end
Expand Down
6 changes: 6 additions & 0 deletions sorbet/custom/stripe.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ class Stripe::Price
sig { returns(Stripe::Price).params(id: T.any(String, T::Hash[Symbol, T.untyped]), opts: T.nilable(T::Hash[Symbol, T.untyped])) }
def self.retrieve(id, opts={}); end

sig { returns(Stripe::Price).params(params: T.nilable(T::Hash[Symbol, T.untyped])) }
def self.construct_from(params={}); end

sig { returns(Integer) }
def unit_amount; end

sig { returns(String) }
def unit_amount_decimal; end

sig { params(arg: T.any(String, BigDecimal)).void }
def unit_amount_decimal=(arg); end

sig { returns(String) }
def billing_scheme; end

Expand Down
1 change: 1 addition & 0 deletions test/integration/translate/test_duplicate_price.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class Critic::DuplicatePriceTranslation < Critic::FunctionalTest
new_price_metadata = new_price.metadata.to_hash
assert_equal("true", new_price_metadata.delete(:salesforce_auto_archive))
assert_equal("true", new_price_metadata.delete(:salesforce_duplicate))
assert_equal(recurring_price.id, new_price_metadata.delete(:salesforce_original_stripe_price_id))
assert_equal(recurring_price.metadata.to_hash, new_price_metadata)
end

Expand Down

0 comments on commit e0d8d42

Please sign in to comment.