Skip to content

Commit

Permalink
[Coupon] Stripe Coupon Beta translation (#862)
Browse files Browse the repository at this point in the history
  • Loading branch information
nadaismail-stripe authored Nov 10, 2022
1 parent c806af9 commit c05c36e
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ workflows:
# unpackaged source test, we do not want a package installed here
- [email protected]
# QA package test, all of the fields/rest endpoints/etc have a prefix
- [email protected]
# - [email protected]
# production package test, has a different prefix from QA
# - [email protected]

Expand Down
15 changes: 15 additions & 0 deletions lib/stripe-force/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ module Constants
SF_PRODUCT_CONSUMPTION_SCHEDULE = 'ProductConsumptionSchedule'
SF_CONSUMPTION_RATE = 'ConsumptionRate'
SF_CONTRACT = 'Contract'
SF_STRIPE_COUPON = 'Stripe_Coupon_Beta__c'
SF_STRIPE_COUPON_QUOTE_LINE_ASSOCIATION = 'Stripe_Coupon_Beta_Quote_Line_Associatio__c'

SF_ID = 'Id'
SF_LAST_MODIFIED_DATE = 'LastModifiedDate'
Expand All @@ -33,6 +35,7 @@ module Constants
SF_CONTRACT_QUOTE_ID = 'SBQQ__Quote__c'

CPQ_QUOTE = 'SBQQ__Quote__c'
CPQ_QUOTE_LINE = 'SBQQ__QuoteLine__c'
CPQ_CONSUMPTION_SCHEDULE = 'SBQQ__OrderItemConsumptionSchedule__c'
CPQ_CONSUMPTION_RATE = 'SBQQ__OrderItemConsumptionRate__c'

Expand Down Expand Up @@ -92,6 +95,18 @@ class CPQProductBillingTypeOptions < T::Enum
ORDER_SUBSCRIPTION_PAYMENT_LINK = 'Stripe_Subscription_Payment_Link__c'

SYNC_RECORD = 'Sync_Record__c'

class SalesforceStripeCouponFields < T::Enum
enums do
NAME = new('Name__c')
PERCENT_OFF = new('Percent_Off__c')
AMOUNT_OFF = new('Amount_Off__c')
DURATION = new('Duration__c')
DURATION_IN_MONTHS = new('Duration_In_Months__c')
MAX_REDEMPTIONS = new('Max_Redemptions__c')
end
end

class SyncRecordFields < T::Enum
enums do
COMPOUND_ID = new('Compound_ID__c')
Expand Down
10 changes: 10 additions & 0 deletions lib/stripe-force/db/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ def required_mappings
"price_order_item" => {
"unit_amount_decimal" => 'UnitPrice',
},
"coupon" => {},
}
end

Expand Down Expand Up @@ -206,6 +207,15 @@ def default_mappings
},

"invoice" => {},

"coupon" => {
"name" => "Name__c",
"amount_off" => "Amount_Off__c",
"percent_off" => "Percent_Off__c",
"duration" => "Duration__c",
"duration_in_months" => "Duration_In_Months__c",
"max_redemptions" => "Max_Redemptions__c",
},
}
end

Expand Down
16 changes: 16 additions & 0 deletions lib/stripe-force/translate/coupon.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true
# typed: true

class StripeForce::Translate
def translate_coupon(sf_coupon)
locker.lock_salesforce_record(sf_coupon)

log.info 'translating coupon', salesforce_object: sf_coupon

coupon = create_stripe_object(Stripe::Coupon, sf_coupon)

update_sf_stripe_id(sf_coupon, coupon)

coupon
end
end
3 changes: 3 additions & 0 deletions lib/stripe-force/translate/translate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ def translate(sf_object)
translate_pricebook(sf_object)
when SF_ACCOUNT
translate_account(sf_object)
when prefixed_stripe_field(SF_STRIPE_COUPON)
translate_coupon(sf_object)
else
raise "unsupported translation type #{sf_object.sobject_type}"
end
Expand Down Expand Up @@ -301,6 +303,7 @@ def throw_user_failure!(salesforce_object:, message:, error_class: nil)
sig { params(sf_object: T.untyped, stripe_object: Stripe::APIResource, additional_salesforce_updates: Hash).void }
def update_sf_stripe_id(sf_object, stripe_object, additional_salesforce_updates: {})
stripe_id_field = prefixed_stripe_field(GENERIC_STRIPE_ID)

stripe_object_id = stripe_object.id

if sf_object[stripe_id_field]
Expand Down
1 change: 1 addition & 0 deletions salesforce_devtips.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- `sfdx force:mdapi:listmetadata -m Layout` to get all layouts on an account. If you want to pull a namespaced layout: `sfdx force:source:retrieve -m "Layout:Account-SBQQ__CPQ Account Layout"`
- Pull custom field from an account: `sfdx force:source:retrieve -m CustomField:Order.Stripe_Transaction_ID__c`
- Lots of debugging info `sfdx force:source:retrieve -m "Layout:Contract-CPQ Contract Layout" --verbose -u cpq-dev --apiversion=55.0 --dev-debug --loglevel=trace`
- Pull a custom object `sfdx force:source:retrieve -m 'CustomObject:Stripe_Coupon_Beta_Quote_Line_Associatio__c`

## Lighting Web Componetns

Expand Down
23 changes: 23 additions & 0 deletions sorbet/custom/stripe.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,27 @@ class Stripe::TestHelpers::TestClock

sig { returns(Integer)}
def frozen_time; end
end

class Stripe::Coupon
sig { returns(String)}
def id; end

sig { returns(Integer)}
def percent_off; end

sig { returns(Integer)}
def amount_off; end

sig { returns(String)}
def duration; end

sig { returns(String)}
def duration_in_months; end

sig { returns(String)}
def max_redemptions; end

sig { returns(String)}
def currency; end
end
91 changes: 91 additions & 0 deletions test/integration/translate/test_coupon.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# frozen_string_literal: true
# typed: true

require_relative '../../test_helper'

class Critic::CouponTranslation < Critic::FunctionalTest
before do
@user = make_user(save: true)
end

it 'translate an SF coupon with percent off set' do
# setup
COUPON_NAME = '100% off coupon'
COUPON_PERCENT_OFF = 100
COUPON_MAX_REDEMPTIONS = 5

# create the SF Stripe coupon
sf_coupon_id = create_salesforce_stripe_coupon(additional_fields: {
SalesforceStripeCouponFields::NAME => COUPON_NAME,
SalesforceStripeCouponFields::PERCENT_OFF => COUPON_PERCENT_OFF,
})

# translate the SF coupon
StripeForce::Translate.perform_inline(@user, sf_coupon_id)

# confirm the Stripe ID was written back into the SF coupon obj
sf_coupon = sf.find(SF_STRIPE_COUPON, sf_coupon_id)
stripe_coupon_id = sf_coupon[prefixed_stripe_field(GENERIC_STRIPE_ID)]
assert(stripe_coupon_id)

# retrieve the created Stripe coupon
stripe_coupon = Stripe::Coupon.retrieve(stripe_coupon_id, @user.stripe_credentials)

# compare the created Stripe coupon with the SF coupon
assert_equal(COUPON_NAME, stripe_coupon.name)
assert_equal(COUPON_PERCENT_OFF, stripe_coupon.percent_off)
assert_equal('once', stripe_coupon.duration)
assert_nil(stripe_coupon.currency)
assert_equal(sf_coupon.Id, stripe_coupon.metadata['salesforce_stripe_coupon_beta_id'])
assert_match(sf_coupon.Id, stripe_coupon.metadata['salesforce_stripe_coupon_beta_link'])
end

it 'verify multiple SF coupons are attached to a quote line' do
# setup
PRODUCT_PRICE = 100
sf_account_id = create_salesforce_account
sf_product_id, _sf_pricebook_id = salesforce_recurring_product_with_price(price: PRODUCT_PRICE)

# create a SF CPQ quote
sf_quote_id = create_salesforce_quote(sf_account_id: sf_account_id, additional_quote_fields: {
CPQ_QUOTE_SUBSCRIPTION_START_DATE => now_time_formatted_for_salesforce,
CPQ_QUOTE_SUBSCRIPTION_TERM => 12.0,
})

# create a sf quote with a product
quote_with_product = add_product_to_cpq_quote(sf_quote_id, sf_product_id: sf_product_id)
calculate_and_save_cpq_quote(quote_with_product)

# retrieve the quote line
quote_lines = sf_get_related(sf_quote_id, CPQ_QUOTE_LINE)
assert_equal(1, quote_lines.size)
quote_line_id = quote_lines.first.Id

# create a coupon and attach to the quote line
sf_coupon_id_1 = create_salesforce_stripe_coupon(additional_fields: {
SalesforceStripeCouponFields::NAME => '25% off coupon',
SalesforceStripeCouponFields::PERCENT_OFF => 25,
})

sf_coupon_id_2 = create_salesforce_stripe_coupon(additional_fields: {
SalesforceStripeCouponFields::NAME => '$10 off coupon',
SalesforceStripeCouponFields::AMOUNT_OFF => 10,
})

# create the association object to map the coupon to the quote line
create_salesforce_stripe_coupon_quote_line_association(sf_quote_line_id: quote_line_id, sf_stripe_coupon_id: sf_coupon_id_1)
create_salesforce_stripe_coupon_quote_line_association(sf_quote_line_id: quote_line_id, sf_stripe_coupon_id: sf_coupon_id_2)

# note: the quote line does not have a reference to the stripe coupon quote line association object
# so we query for the association objects that have a reference to this quote line
associated_coupons = get_salesforce_stripe_coupons_associated_to_quote_line(quote_line_id: quote_line_id)
assert(2, associated_coupons.size)

coupon_1 = associated_coupons.first.Name_c == '25% off coupon' ? associated_coupons[0] : associated_coupons[1]
assert(25, coupon_1.Percent_Off__c)
end

it 'translate order with coupon' do
# TODO https://jira.corp.stripe.com/browse/PLATINT-1952
end
end
41 changes: 41 additions & 0 deletions test/support/salesforce_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,47 @@ def create_salesforce_product(additional_fields: {})
}.merge(additional_fields))
end

def create_salesforce_stripe_coupon_quote_line_association(sf_quote_line_id:, sf_stripe_coupon_id:)
sf_stripe_coupon_id ||= create_salesforce_stripe_coupon

sf.create!(prefixed_stripe_field(SF_STRIPE_COUPON_QUOTE_LINE_ASSOCIATION), {
"Quote_Line__c" => sf_quote_line_id,
"Stripe_Coupon__c" => sf_stripe_coupon_id,
}.transform_keys(&method(:prefixed_stripe_field)))
end

def create_salesforce_stripe_coupon(additional_fields: {})
# we want to ensure that either amount_off or percent_off is set and not both
amount_off = nil
if additional_fields[SalesforceStripeCouponFields::PERCENT_OFF].nil?
amount_off = TEST_DEFAULT_PRICE / 2
end

sf.create!(prefixed_stripe_field(SF_STRIPE_COUPON), {
SalesforceStripeCouponFields::NAME => sf_randomized_name(SF_STRIPE_COUPON),
SalesforceStripeCouponFields::AMOUNT_OFF => amount_off,
SalesforceStripeCouponFields::DURATION => 'once',
}.merge(additional_fields).transform_keys(&:serialize).transform_keys(&method(:prefixed_stripe_field)))
end

def get_salesforce_stripe_coupons_associated_to_quote_line(quote_line_id:)
quote_line_associations = sf.query("Select Id from #{prefixed_stripe_field(SF_STRIPE_COUPON_QUOTE_LINE_ASSOCIATION)} where #{prefixed_stripe_field('Quote_Line__c')} = '#{quote_line_id}'")

if !quote_line_associations
raise "could not find any stripe coupon quote line associations related to this quote line"
end

# there could be multiple coupons associated with a quote line
coupons = quote_line_associations.map do |quote_line_association|
association = sf.find(prefixed_stripe_field(SF_STRIPE_COUPON_QUOTE_LINE_ASSOCIATION), quote_line_association.Id)
coupon_id = association[prefixed_stripe_field('Stripe_Coupon__c')]

# return the coupon object
sf.find(prefixed_stripe_field(SF_STRIPE_COUPON), coupon_id)
end
coupons
end

def salesforce_recurring_metered_produce_with_price(price_in_cents: nil)
salesforce_recurring_product_with_price(
price: price_in_cents,
Expand Down

0 comments on commit c05c36e

Please sign in to comment.