Skip to content

Commit

Permalink
[Coupons] Translate coupon on quote / order (#906)
Browse files Browse the repository at this point in the history
  • Loading branch information
nadaismail-stripe authored Nov 23, 2022
1 parent 5752c0b commit 6ccc1ff
Show file tree
Hide file tree
Showing 14 changed files with 621 additions and 85 deletions.
3 changes: 3 additions & 0 deletions lib/stripe-force/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ module Constants
SF_CONTRACT = 'Contract'
SF_STRIPE_COUPON = 'Stripe_Coupon_Beta__c'
SF_STRIPE_COUPON_SERIALIZED = 'Stripe_Coupon_Beta_Serialized__c'
SF_STRIPE_COUPON_QUOTE_ASSOCIATION = 'Stripe_Coupon_Beta_Quote_Association__c'
SF_STRIPE_COUPON_QUOTE_LINE_ASSOCIATION = 'Stripe_Coupon_Beta_Quote_Line_Associatio__c'
SF_STRIPE_COUPON_ORDER_ASSOCIATION = 'Stripe_Coupon_Beta_Order_Association__c'
SF_STRIPE_COUPON_ORDER_ITEM_ASSOCIATION = 'Stripe_Coupon_Beta_Order_Item_Associatio__c'

SF_ID = 'Id'
Expand Down Expand Up @@ -184,6 +186,7 @@ class FeatureFlags < T::Enum
CATCH_ALL_ERRORS = new('catch_all_errors')
UPDATE_CUSTOMER_ON_ORDER_TRANSLATION = new('update_customer_on_order_creation')
ACCOUNT_POLLING = new('account_polling')
COUPONS = new('coupons')
end
end

Expand Down
63 changes: 39 additions & 24 deletions lib/stripe-force/translate/coupon.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ def translate_coupon(sf_coupon)

catch_errors_with_salesforce_context(secondary: sf_coupon) do
create_coupon_from_sf_coupon(sf_coupon)
ensure
locker.release_salesforce_record_lock(sf_coupon)
end
end

def create_coupon_from_sf_coupon(sf_coupon)
# check if the original sf coupon has been translated prior and exists in Stripe
# this is the coupon that the serialized coupon on the order/order item was copied from

# TODO hook up to cache service
original_sf_coupon = sf.find(SF_STRIPE_COUPON, sf_coupon.Original_Stripe_Coupon_Beta_Id__c)
# this original coupon is the coupon that the order / order item coupon was copied from
original_sf_coupon = sf.find(prefixed_stripe_field(SF_STRIPE_COUPON), sf_coupon[prefixed_stripe_field("Original_Stripe_Coupon_Beta_Id__c").to_s])
existing_stripe_coupon = retrieve_from_stripe(Stripe::Coupon, original_sf_coupon)
if existing_stripe_coupon
existing_stripe_coupon = T.cast(existing_stripe_coupon, Stripe::Coupon)
Expand All @@ -26,7 +26,7 @@ def create_coupon_from_sf_coupon(sf_coupon)
# this should never happen unless the coupon data in Salesforce is mutated
# if so, we want to create a new Stripe object and write back the new id
if coupons_are_equal?(existing_stripe_coupon: existing_stripe_coupon, generated_stripe_coupon: generated_stripe_coupon)
log.info 'using existing stripe coupon', existing_stripe_coupon_id: existing_stripe_coupon.id
log.info 'reusing existing stripe coupon', existing_stripe_coupon_id: existing_stripe_coupon.id
return existing_stripe_coupon
end
end
Expand Down Expand Up @@ -60,39 +60,54 @@ def coupons_are_equal?(existing_stripe_coupon:, generated_stripe_coupon:)
simple_field_check_passed
end

# Coupon can be related to an order or an order item. Either way, Stripe expects these coupons to be specified as discounts:
# https://site-admin.stripe.com/docs/api/subscription_schedules/object#subscription_schedule_object-phases-discounts
def discounts_from_sf_order_item(sf_order_item:)
sf_coupons = StripeForce::Translate.get_salesforce_stripe_coupons_associated_to_order_line(sf_client: @user.sf_client, sf_order_line_id: sf_order_item.Id)
if !sf_coupons || sf_coupons.empty?
def stripe_discounts_for_sf_object(sf_object:)
sf_coupons = get_salesforce_stripe_coupons_associated_to_sf_object(sf_client: @user.sf_client, sf_object: sf_object)

if sf_coupons.nil?
return
end

log.info 'found coupons for sf order item', salesforce_object: sf_order_item
log.info 'found coupons for sf object', salesforce_object: sf_object

# this is the format the stripe api expects
sf_coupons.map do |sf_coupon|
coupon = translate_coupon(sf_coupon)
{coupon: coupon.id}
end
end

def self.get_salesforce_stripe_coupons_associated_to_order_line(sf_client:, sf_order_line_id:)
order_item_associations = sf_client.query("Select Id from #{SF_STRIPE_COUPON_ORDER_ITEM_ASSOCIATION} where Order_Item__c = '#{sf_order_line_id}'")
def get_salesforce_stripe_coupons_associated_to_sf_object(sf_client:, sf_object:)
catch_errors_with_salesforce_context(secondary: sf_object) do
# coupons can either be related to an order or order item
if sf_object.sobject_type == SF_ORDER
association_obj_type = SF_STRIPE_COUPON_ORDER_ASSOCIATION
association_field = 'Order__c'
elsif sf_object.sobject_type == SF_ORDER_ITEM
association_obj_type = SF_STRIPE_COUPON_ORDER_ITEM_ASSOCIATION
association_field = 'Order_Item__c'
else
# this should never happen since coupons can only be tied to an order or order item
raise "unsupported sf object type for coupons #{sf_object.sobject_type}"
end

if !order_item_associations || order_item_associations.size == 0
log.info "no stripe coupon order line associations related to this order line", salesforce_object: sf_order_line_id
return
end
associations = sf_client.query("Select Id from #{prefixed_stripe_field(association_obj_type)} where #{prefixed_stripe_field(association_field)} = '#{sf_object.Id}'")

# there could be multiple coupons associated with a single order line
coupons = order_item_associations.map do |order_item_association|
association = sf_client.find(SF_STRIPE_COUPON_ORDER_ITEM_ASSOCIATION, order_item_association.Id)
coupon = sf_client.query("Select Id from #{SF_STRIPE_COUPON_SERIALIZED} where Id = '#{association.Stripe_Coupon__c}'")
if !associations || associations.size == 0
log.info "no stripe coupon associations related to this sf object", salesforce_object: sf_object
return
end

# return the coupon object
sf_client.find(SF_STRIPE_COUPON_SERIALIZED, coupon.first.Id)
end
# there could be multiple coupons associated with a single order line
coupons = associations.map do |association|
association = sf_client.find(association_obj_type, association.Id)
coupon = sf_client.query("Select Id from #{prefixed_stripe_field(SF_STRIPE_COUPON_SERIALIZED)} where Id = '#{association.Stripe_Coupon__c}'")

coupons
# return the coupon object
sf_client.find(prefixed_stripe_field(SF_STRIPE_COUPON_SERIALIZED), coupon.first.Id)
end

coupons
end
end
end
9 changes: 8 additions & 1 deletion lib/stripe-force/translate/order.rb
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ def generate_phases_for_initial_order(sf_order:, invoice_items:, subscription_it
metadata: Metadata.stripe_metadata_for_sf_object(@user, sf_order),
}

if @user.feature_enabled?(FeatureFlags::COUPONS)
initial_phase["discounts"] = stripe_discounts_for_sf_object(sf_object: sf_order)
end

prorated_phase = nil

# TODO this needs to be gated and synced with the specific flag that CF is using
Expand Down Expand Up @@ -698,9 +702,12 @@ def phase_items_from_order_lines(sf_order_lines)
phase_item = Stripe::SubscriptionItem.construct_from({
price: price.id,
metadata: Metadata.stripe_metadata_for_sf_object(@user, sf_order_item),
discounts: discounts_from_sf_order_item(sf_order_item: sf_order_item),
})

if @user.feature_enabled?(FeatureFlags::COUPONS)
phase_item.discounts = stripe_discounts_for_sf_object(sf_object: sf_order_item)
end

phase_item_params = StripeForce::Utilities::SalesforceUtil.extract_salesforce_params!(mapper, sf_order_item, Stripe::SubscriptionItem)
mapper.assign_values_from_hash(phase_item, phase_item_params)
apply_mapping(phase_item, sf_order_item)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<actionOverrides>
<actionName>Accept</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Accept</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Accept</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>CancelEdit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>CancelEdit</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>CancelEdit</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Clone</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Clone</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Clone</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Delete</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Delete</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Delete</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Edit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Edit</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Edit</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>List</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>List</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>List</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>New</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>New</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>New</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>SaveEdit</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>SaveEdit</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>SaveEdit</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Tab</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Tab</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>Tab</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>View</actionName>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>View</actionName>
<formFactor>Large</formFactor>
<type>Default</type>
</actionOverrides>
<actionOverrides>
<actionName>View</actionName>
<formFactor>Small</formFactor>
<type>Default</type>
</actionOverrides>
<allowInChatterGroups>false</allowInChatterGroups>
<compactLayoutAssignment>SYSTEM</compactLayoutAssignment>
<deploymentStatus>Deployed</deploymentStatus>
<description>Custom junction object linking a serialized Stripe Coupon and an Order Line.</description>
<enableActivities>false</enableActivities>
<enableBulkApi>true</enableBulkApi>
<enableFeeds>false</enableFeeds>
<enableHistory>false</enableHistory>
<enableLicensing>false</enableLicensing>
<enableReports>false</enableReports>
<enableSearch>false</enableSearch>
<enableSharing>true</enableSharing>
<enableStreamingApi>true</enableStreamingApi>
<externalSharingModel>ControlledByParent</externalSharingModel>
<label>Stripe Coupon Beta Order Associatio</label>
<nameField>
<displayFormat>{0000}</displayFormat>
<label>Stripe Coupon Beta Order Associatio Name</label>Quot
<type>AutoNumber</type>
</nameField>
<pluralLabel>Stripe Coupon Beta Order</pluralLabel>
<searchLayouts></searchLayouts>
<sharingModel>ControlledByParent</sharingModel>
<validationRules>
<fullName>Order_Field_Must_Be_Set</fullName>
<description>Order__c is a required field.</description>
<active>true</active>
<errorConditionFormula>ISBLANK(Order__c)</errorConditionFormula>
<errorMessage>Order__c is a required field.</errorMessage>
</validationRules>
<visibility>Public</visibility>
</CustomObject>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Order__c</fullName>
<externalId>false</externalId>
<label>Order</label>
<referenceTo>Order</referenceTo>
<relationshipLabel>Stripe Coupon Beta Order Connection</relationshipLabel>
<relationshipName>Stripe_Coupon_Beta_Order_Connection</relationshipName>
<reparentableMasterDetail>false</reparentableMasterDetail>
<trackTrending>false</trackTrending>
<type>Lookup</type>
</CustomField>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
<fullName>Stripe_Coupon__c</fullName>
<externalId>false</externalId>
<label>Stripe Coupon</label>
<referenceTo>Stripe_Coupon_Beta_Serialized__c</referenceTo>
<relationshipLabel>Stripe Coupon Beta Serialized Order Link</relationshipLabel>
<relationshipName>Stripe_Coupon_Beta_Serialized_Order_Link</relationshipName>
<relationshipOrder>0</relationshipOrder>
<reparentableMasterDetail>false</reparentableMasterDetail>
<trackTrending>false</trackTrending>
<type>MasterDetail</type>
<writeRequiresMasterRead>true</writeRequiresMasterRead>
</CustomField>
Loading

0 comments on commit 6ccc1ff

Please sign in to comment.