Skip to content

Commit

Permalink
Merge pull request #151 from RyanofWoods/ryanofwoods/add-venmo-standa…
Browse files Browse the repository at this point in the history
…lone

Change Venmo integration to use Venmo standalone
  • Loading branch information
DanielePalombo authored Dec 22, 2021
2 parents 00ec566 + 961f0cf commit 64be7dd
Show file tree
Hide file tree
Showing 16 changed files with 198 additions and 65 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,12 @@ PayPals API does not allow for admin-side payments. Instead, backend users takin
## Venmo
Venmo is currently available to US merchants and buyers. There are also other [prequisites](https://developer.paypal.com/docs/business/checkout/pay-with-venmo/#eligibility).

If the transaction supports Venmo then a button should appear for it on checkout, cart and product page, depending on your `Payment Method` preferences.
If the transaction supports Venmo and it is enabled by the following, then a button should appear for it on checkout payment page. Note, Venmo cannot currently be rendered on the product or cart pages.

By default, the extension and PayPal will try to render a Venmo button to buyers when prequisites are met.

Set the PaypalCommercePlatform `PaymentMethod` `venmo_control` preference to:
- `enforced`, disregard buyer's location and show button (if other prequisites are met); or
- `enabled` (default), available as a PayPal funding option (if other prequisites are met); or
- `disabled`, disable Venmo as a funding option.
Set the PaypalCommercePlatform `PaymentMethod` `venmo_standalone` preference to:
- `render only standalone`, show Venmo on the payment page and do not show the other funding sources (i.e. PayPal, Credit); or
- `enabled`, show Venmo on the payment page; or
- `disabled` (default), do not show the Venmo button.

See more about preferences([Configuration](#configuration)) below.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ SolidusPaypalCommercePlatform.approveOrder = function(data, actions) {
SolidusPaypalCommercePlatform.verifyTotal(response.purchase_units[0].amount.value).then(function(){
$("#payments_source_paypal_order_id").val(data.orderID)
$("#payments_source_paypal_email").val(response.payer.email_address)
$("#payments_source_paypal_funding_source").val(SolidusPaypalCommercePlatform.fundingSource)
$("#checkout_form_payment").submit()
})
})
Expand Down Expand Up @@ -168,7 +169,8 @@ SolidusPaypalCommercePlatform.addPayment = function(paypal_amount, payment_metho
payment_method_id: payment_method_id,
source_attributes: {
paypal_order_id: data.orderID,
paypal_email: email
paypal_email: email,
paypal_funding_source: SolidusPaypalCommercePlatform.fundingSource
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ SolidusPaypalCommercePlatform.renderButton = function(payment_method_id, style)
paypal.Buttons({
style: style,
createOrder: SolidusPaypalCommercePlatform.sendOrder.bind(null, payment_method_id),
onClick: (data) => { SolidusPaypalCommercePlatform.fundingSource = data.fundingSource },
onApprove: SolidusPaypalCommercePlatform.approveOrder,
onShippingChange: SolidusPaypalCommercePlatform.shippingChange,
onError: SolidusPaypalCommercePlatform.handleError
Expand All @@ -12,6 +13,7 @@ SolidusPaypalCommercePlatform.renderCartButton = function(payment_method_id, sty
paypal.Buttons({
style: style,
createOrder: SolidusPaypalCommercePlatform.sendOrder.bind(null, payment_method_id),
onClick: (data) => { SolidusPaypalCommercePlatform.fundingSource = data.fundingSource },
onApprove: SolidusPaypalCommercePlatform.finalizeOrder.bind(null, payment_method_id),
onShippingChange: SolidusPaypalCommercePlatform.shippingChange,
onError: SolidusPaypalCommercePlatform.handleError
Expand All @@ -22,8 +24,20 @@ SolidusPaypalCommercePlatform.renderProductButton = function(payment_method_id,
paypal.Buttons({
style: style,
createOrder: SolidusPaypalCommercePlatform.createAndSendOrder.bind(null, payment_method_id),
onClick: (data) => { SolidusPaypalCommercePlatform.fundingSource = data.fundingSource },
onApprove: SolidusPaypalCommercePlatform.finalizeOrder.bind(null, payment_method_id),
onShippingChange: SolidusPaypalCommercePlatform.shippingChange,
onError: SolidusPaypalCommercePlatform.handleError
}).render('#paypal-button-container')
}

SolidusPaypalCommercePlatform.renderVenmoStandalone = function(payment_method_id, style) {
paypal.Buttons({
style: style,
fundingSource: paypal.FUNDING.VENMO,
createOrder: SolidusPaypalCommercePlatform.sendOrder.bind(null, payment_method_id),
onClick: () => { SolidusPaypalCommercePlatform.fundingSource = paypal.FUNDING.VENMO },
onApprove: SolidusPaypalCommercePlatform.approveOrder,
onError: SolidusPaypalCommercePlatform.handleError
}).render('#paypal-button-container')
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ def preferred_paypal_button_shape_options
def preferred_paypal_button_layout_options
[["Vertical", "vertical"], ["Horizontal", "horizontal"]]
end

def preferred_venmo_standalone_options
[['Render only standalone', 'render only standalone'], ['Enabled', 'Enabled'], ['Disabled', 'disabled']]
end
end
end
21 changes: 14 additions & 7 deletions app/models/solidus_paypal_commerce_platform/payment_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,21 @@ class PaymentMethod < SolidusSupport.payment_method_parent_class
preference :display_on_cart, :boolean, default: true
preference :display_on_product_page, :boolean, default: true
preference :display_credit_messaging, :boolean, default: true
preference :venmo_control, :string, default: 'enabled'
preference :venmo_standalone, :paypal_select, default: 'disabled'
preference :force_buyer_country, :string

validates :preferred_venmo_control, inclusion: {
in: %w[enforced enabled disabled],
message: "must be 'enforced', 'enabled' or 'disabled'."
}
validates :preferred_paypal_button_color, exclusion: {
in: %w[gold],
message: "cannot be 'gold' when Venmo standalone is enabled."
}, if: :venmo_standalone_enabled?

def venmo_standalone_enabled?
options[:venmo_standalone] != 'disabled'
end

def render_only_venmo_standalone?
options[:venmo_standalone] == 'render only standalone'
end

def partial_name
"paypal_commerce_platform"
Expand Down Expand Up @@ -80,8 +88,7 @@ def javascript_sdk_url(order: nil, currency: nil)
}

parameters[:shipping_preference] = 'NO_SHIPPING' if step_names.exclude? 'delivery'
parameters['enable-funding'] = 'venmo' if options[:venmo_control] == 'enforced'
parameters['disable-funding'] = 'venmo' if options[:venmo_control] == 'disabled'
parameters['enable-funding'] = 'venmo' if venmo_standalone_enabled?

if !Rails.env.production? && options[:force_buyer_country].present?
parameters['buyer-country'] = options[:force_buyer_country]
Expand Down
11 changes: 11 additions & 0 deletions app/models/solidus_paypal_commerce_platform/payment_source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,19 @@
module SolidusPaypalCommercePlatform
class PaymentSource < SolidusSupport.payment_source_parent_class
self.table_name = "paypal_commerce_platform_sources"
enum paypal_funding_source: {
applepay: 0, bancontact: 1, blik: 2, boleto: 3, card: 4, credit: 5, eps: 6, giropay: 7, ideal: 8,
itau: 9, maxima: 10, mercadopago: 11, mybank: 12, oxxo: 13, p24: 14, paylater: 15, paypal: 16, payu: 17,
sepa: 18, sofort: 19, trustly: 20, venmo: 21, verkkopankki: 22, wechatpay: 23, zimpler: 24
}, _suffix: :funding
validates :paypal_order_id, :payment_method_id, presence: true

def display_paypal_funding_source
I18n.t(paypal_funding_source,
scope: 'solidus_paypal_commerce_platform.paypal_funding_sources',
default: paypal_funding_source)
end

def actions
%w(capture void credit)
end
Expand Down
29 changes: 28 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ en:
paypal_capture_id: PayPal Capture ID
paypal_debug_id: PayPal Debug ID
paypal_email: PayPal Email
paypal_funding: 'PayPal Funding: %{funding}'
paypal_funding_source: 'PayPal Funding Source'
payment_email: "Payment Email: %{email}"
virtual_terminal_notice_html: Backend Credit Card payments can only be accepted through PayPal via the <a href="https://www.paypal.com/merchantapps/appcenter/acceptpayments/virtualterminal">PayPal Virtual Terminal</a>.
solidus_paypal_commerce_platform:
Expand All @@ -34,4 +36,29 @@ en:
success: "Payment refunded for %{amount}"
pay_pal_checkout_sdk/payments/authorizations_void_request:
success: "Payment voided"

paypal_funding_sources:
applepay: 'Apple Pay'
bancontact: 'Bancontact'
blik: 'BLIK'
boleto: 'Boleto'
card: 'Credit or debit card'
credit: 'PayPal Credit'
eps: 'eps'
giropay: 'giropay'
ideal: 'iDEAL'
itau: 'Itau'
maxima: 'Maxima'
mercadopago: 'Mercado Pago'
mybank: 'MyBank'
oxxo: 'Oxxo'
p24: 'Przelewy24'
paylater: 'Pay Later'
paypal: 'PayPal'
payu: 'PayU'
sepa: 'SEPA-Lastschrift'
sofort: 'Sofort'
trustly: 'Trustly'
venmo: 'Venmo'
verkkopankki: 'Verkkopankki'
wechatpay: 'WeChat Pay'
zimpler: 'Zimpler'
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddPaypalFundingSourceToPaypalCommercePlatformSources < ActiveRecord::Migration[6.1]
def change
add_column :paypal_commerce_platform_sources, :paypal_funding_source, :integer
end
end
3 changes: 2 additions & 1 deletion lib/solidus_paypal_commerce_platform/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class Engine < Rails::Engine
initializer "solidus_paypal_commerce_platform.add_payment_method", after: "spree.register.payment_methods" do |app|
app.config.spree.payment_methods << SolidusPaypalCommercePlatform::PaymentMethod
SolidusPaypalCommercePlatform::PaymentMethod.allowed_admin_form_preference_types << :paypal_select
Spree::PermittedAttributes.source_attributes.concat [:paypal_order_id, :authorization_id, :paypal_email]
Spree::PermittedAttributes.source_attributes.concat [:paypal_order_id, :authorization_id,
:paypal_email, :paypal_funding_source]
end

initializer "solidus_paypal_commerce_platform.add_wizard", after: "spree.register.payment_methods" do |app|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
preferences {
{
client_id: SecureRandom.hex(8),
client_secret: SecureRandom.hex(10)
client_secret: SecureRandom.hex(10),
venmo_standalone: 'disabled'
}
}
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

<dt><%= I18n.t("paypal_email") %>:</dt>
<dd><%= payment.source.paypal_email %></dd>

<dt><%= I18n.t("paypal_funding_source") %>:</dt>
<dd><%= payment.source.display_paypal_funding_source %></dd>
</dl>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<% if payment.source.respond_to?(:paypal_funding_source) %>
<br />
<%= t('paypal_funding', funding: payment.source.display_paypal_funding_source) %>
<% end %>
<% if payment.source.respond_to?(:paypal_email) %>
<br />
<%= t('payment_email', email: payment.source.paypal_email) %>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,27 @@

<input type="hidden" name="payment_source[<%= payment_method.id %>][paypal_order_id]" id="payments_source_paypal_order_id">
<input type="hidden" name="payment_source[<%= payment_method.id %>][paypal_email]" id="payments_source_paypal_email">
<input type="hidden" name="payment_source[<%= payment_method.id %>][paypal_funding_source]" id="payments_source_paypal_funding_source">

<script>
Spree.current_order_id = "<%= @order.number %>"
Spree.current_order_token = "<%= @order.guest_token %>"
$( document ).ready(function() {
SolidusPaypalCommercePlatform.renderButton("<%= payment_method.id %>",<%= raw payment_method.button_style.to_json %>)
})
</script>

<% unless payment_method.render_only_venmo_standalone? %>
<script>
$( document ).ready(function() {
SolidusPaypalCommercePlatform.renderButton("<%= payment_method.id %>",<%= raw payment_method.button_style.to_json %>)
})
</script>
<% end %>
<% if payment_method.venmo_standalone_enabled? %>
<script>
$( document ).ready(function() {
SolidusPaypalCommercePlatform.renderVenmoStandalone("<%= payment_method.id %>",<%= raw payment_method.button_style.to_json %>)
})
</script>
<% end %>

<div id="paypal_commerce_platform_overlay"></div>
10 changes: 10 additions & 0 deletions spec/features/frontend/checkout_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@
click_button("Save and Continue")
expect(Spree::Payment.last.source.paypal_email).to eq "[email protected]"
end

it "records the paypal funding source" do
visit '/checkout/payment'
choose(option: paypal_payment_method.id)
find(:xpath, "//input[@id='payments_source_paypal_order_id']", visible: false).set SecureRandom.hex(8)
find(:xpath, "//input[@id='payments_source_paypal_email']", visible: false).set "[email protected]"
find(:xpath, "//input[@id='payments_source_paypal_funding_source']", visible: false).set "venmo"
click_button("Save and Continue")
expect(Spree::Payment.last.source.paypal_funding_source).to eq "venmo"
end
end

context "when a payment fails" do
Expand Down
69 changes: 25 additions & 44 deletions spec/models/solidus_paypal_commerce_platform/payment_method_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ def Struct(data) # rubocop:disable Naming/MethodName

before { allow_any_instance_of(PayPal::PayPalHttpClient).to receive(:execute) { response } }

describe 'preferences' do
context 'with paypal_button_color' do
before do
paypal_payment_method.preferences.update(paypal_button_color: 'gold')
paypal_payment_method.save
end

it 'cannot be gold when Venmo standalone is enabled' do
expect(paypal_payment_method).to be_valid
paypal_payment_method.preferences.update(venmo_standalone: 'enabled')
expect(paypal_payment_method).to be_invalid
paypal_payment_method.preferences.update(venmo_standalone: 'only render standalone')
expect(paypal_payment_method).to be_invalid
end
end
end

describe "#purchase" do
let(:result) { Struct(purchase_units: [Struct(payments: payments)]) }
let(:payments) { Struct(captures: [Struct(id: SecureRandom.hex(4))]) }
Expand Down Expand Up @@ -114,64 +131,28 @@ def Struct(data) # rubocop:disable Naming/MethodName
end
end

describe 'preferences' do
context 'with venmo_control' do
it 'is "default" by default' do
expect(paypal_payment_method.preferred_venmo_control).to eq('enabled')
end

it 'only allows "enforced", "enabled" and "disabled" as values', :aggregate_failures do
expect(paypal_payment_method).to be_valid
valid_values = %w[enforced enabled disabled]
invalid_values = %w[foo bar]

valid_values.each do |value|
paypal_payment_method.preferred_venmo_control = value
expect(paypal_payment_method).to be_valid
end

invalid_values.each do |value|
paypal_payment_method.preferred_venmo_control = value
expect(paypal_payment_method).to be_invalid
end
end
end
end

context 'when venmo_control is "enforced"' do
before { paypal_payment_method.preferences.update(venmo_control: 'enforced') }
context 'when venmo_standalone is "only render standalone"' do
before { paypal_payment_method.preferences.update(venmo_standalone: 'only render standalone') }

it 'includes "enable-funding=venmo" as a parameter' do
expect(url.query.split('&')).to include('enable-funding=venmo')
end

it 'does not include the "disable-funding" parameter' do
expect(url.query.split('&')).not_to include(match 'disable-funding')
end
end

context 'when venmo_control is "enabled"' do
before { paypal_payment_method.preferences.update(venmo_control: 'enabled') }

it 'include the "enable-funding" parameter' do
expect(url.query.split('&')).not_to include(match 'enable-funding')
end
context 'when venmo_standalone is "enabled"' do
before { paypal_payment_method.preferences.update(venmo_standalone: 'enabled') }

it 'does not include the "disable-funding" parameter' do
expect(url.query.split('&')).not_to include(match 'disable-funding')
it 'includes "enable-funding=venmo" as a parameter' do
expect(url.query.split('&')).to include('enable-funding=venmo')
end
end

context 'when venmo_control is "disabled"' do
before { paypal_payment_method.preferences.update(venmo_control: 'disabled') }
context 'when venmo_standalone is "disabled"' do
before { paypal_payment_method.preferences.update(venmo_standalone: 'disabled') }

it 'does not include the "enable-funding" parameter' do
expect(url.query.split('&')).not_to include(match 'enable-funding')
end

it 'includes "disable-funding=venmo" as a parameter' do
expect(url.query.split('&')).to include('disable-funding=venmo')
end
end

context 'when force_buyer_country is an empty string' do
Expand Down
Loading

0 comments on commit 64be7dd

Please sign in to comment.