Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 61 additions & 46 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,65 +10,80 @@ orbs:
solidusio_extensions: solidusio/extensions@volatile

commands:
test-with-starter-frontend:
run-specs-with-starter-frontend:
steps:
- checkout
- browser-tools/install-chrome
- solidusio_extensions/dependencies
- solidusio_extensions/test-branch:
branch: master
- run:
name: Install libvips
command: |
export FRONTEND=starter
sudo gem update --system
gem install bundler
bin/dummy-app
bin/rspec
- solidusio_extensions/store-test-results
sudo apt-get update
sudo apt-get install -yq libvips-dev
- run:
name: 'Update Rubygems'
command: |
sudo gem update --system --no-document
gem install bundler --no-document
gem environment path
- run:
name: 'Store tool versions'
command: |
rm -rf /tmp/.tool-versions
ruby -v >> /tmp/.tool-versions
gem search -eq rails >> /tmp/.tool-versions # get the latest rails from rubygems
gem search -eq solidus >> /tmp/.tool-versions # get the latest solidus from rubygems
cat /tmp/.tool-versions
- restore_cache:
keys:
- solidus-stripe-gems-v1-{{ checksum "/tmp/.tool-versions" }}
- solidus-stripe-gems-v1-
- run:
name: 'Install gems'
command: |
bundle install
- run:
name: 'Install dummy app'
command: bin/dummy-app
environment:
FRONTEND: starter
SOLIDUS_BRANCH: master
- save_cache:
key: solidus-stripe-gems-v1-{{ checksum "/tmp/.tool-versions" }}
paths:
- /home/circleci/.rubygems
- run:
name: 'Runs specs'
command: |
bin/rspec --format progress --format RspecJunitFormatter --out "$PWD/test-results/results.xml"
- store_test_results:
path: test-results

jobs:
run-specs-with-postgres:
executor:
name: solidusio_extensions/postgres
ruby_version: '3.1'
steps:
- test-with-starter-frontend

run-specs-with-mysql:
run-specs:
parameters:
database:
type: string
default: postgres
ruby:
type: string
default: '3.2'
executor:
name: solidusio_extensions/mysql
ruby_version: '3.0'
name: solidusio_extensions/<< parameters.database >>
ruby_version: << parameters.ruby >>
steps:
- test-with-starter-frontend

run-specs-with-sqlite:
executor:
name: solidusio_extensions/sqlite
ruby_version: '2.7'
steps:
- test-with-starter-frontend

- run-specs-with-starter-frontend
lint-code:
executor: solidusio_extensions/sqlite-memory
steps:
- solidusio_extensions/lint-code

workflows:
"Run specs on supported Solidus versions":
build:
jobs:
- run-specs-with-postgres
- run-specs-with-mysql
- run-specs-with-sqlite
- run-specs:
name: 'run-specs-ruby-<<matrix.ruby>>-db-<<matrix.database>>'
matrix:
parameters:
ruby: ['3.2'] # TODO: ['3.2', '3.1', '3.0']
database: ['sqlite'] # TODO: ['mysql', 'sqlite', 'postgres']
- lint-code

"Weekly run specs against master":
triggers:
- schedule:
cron: "0 0 * * 4" # every Thursday
filters:
branches:
only:
- master
jobs:
- run-specs-with-postgres
- run-specs-with-mysql
- run-specs-with-sqlite
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ gem 'stringio'
gem 'timeout'

gem 'listen'

gem 'activestorage'
4 changes: 2 additions & 2 deletions Procfile.dev
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
web: unset PORT && bin/rails-sandbox server
installer: bin/rails-sandbox g solidus_stripe:install --force --watch
web: unset PORT && env RUBY_DEBUG_OPEN=true bin/rails-sandbox server

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a comment with usage instructions here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call, added instructions to the readme under Development so it's even more visible.

watch: bin/rails-sandbox g solidus_stripe:install --force --watch
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ Retrieve your API Key and Publishable Key from your [Stripe testing dashboard](h
Set `SOLIDUS_STRIPE_API_KEY` and `SOLIDUS_STRIPE_PUBLISHABLE_KEY` environment variables (e.g. via `direnv`), this
will trigger the default initializer to create a static preference for SolidusStripe.

Run `bin/dev` to start both the sandbox rail server and the file watcher that will update the sandbox whenever
a file is changed.
Run `bin/dev` to start both the sandbox rail server and the file watcher through Foreman. That will update the sandbox whenever
a file is changed. When using `bin/dev` you can safely add `debugger` statements, even if Foreman won't provide a TTY, by connecting
to the debugger session through `rdbg --attach` from another terminal.

Visit `/admin/payments` and create a new Stripe payment using the static preferences.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def create
.available_payment_methods.find(params[:payment_method_id])

currency = current_order.currency
amount = SolidusStripe::Gateway::MoneyToStripeAmountConverter.to_stripe_amount(
amount = SolidusStripe::MoneyToStripeAmountConverter.to_stripe_amount(
current_order.display_total.money.fractional,
currency,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# frozen_string_literal: true

module SolidusStripe::MoneyToStripeAmountConverter
extend ActiveSupport::Concern
extend self

ZERO_DECIMAL_CURRENCIES = %w[
BIF
CLP
DJF
GNF
JPY
KMF
KRW
MGA
PYG
RWF
UGX
VND
VUV
XAF
XOF
XPF
].freeze

THREE_DECIMAL_CURRENCIES = %w[
BHD
JOD
KWD
OMR
TND
].freeze

# special currencies that are represented in cents but
# should be divisible by 100, thus making them integer only.
DIVISIBLE_BY_100 = %w[
HUF
TWD
UGX
].freeze

# Solidus will provide a "fractional" amount, that is specific for each currency
# following the configurationo defined in the Money gem.
#
# Stripe uses the "smallest currency unit",
# (e.g., 100 cents to charge $1.00 or 100 to charge ¥100, a zero-decimal currency)
# https://stripe.com/docs/currencies#zero-decimal
#
# We need to ensure the fractional amount is considering the same number of decimals.
def to_stripe_amount(fractional, currency)
solidus_subunit_to_unit, stripe_subunit_to_unit = subunit_to_unit(currency)

if stripe_subunit_to_unit == solidus_subunit_to_unit
fractional
else
(fractional / solidus_subunit_to_unit.to_d) * stripe_subunit_to_unit.to_d
end
end

def to_solidus_amount(fractional, currency)
solidus_subunit_to_unit, stripe_subunit_to_unit = subunit_to_unit(currency)

if stripe_subunit_to_unit == solidus_subunit_to_unit
fractional
else
(fractional / stripe_subunit_to_unit.to_d) * solidus_subunit_to_unit.to_d
end
end

private

def subunit_to_unit(currency)
solidus_subunit_to_unit = ::Money::Currency.new(currency).subunit_to_unit
stripe_subunit_to_unit =
case currency.to_s.upcase
when *ZERO_DECIMAL_CURRENCIES then 1
when *THREE_DECIMAL_CURRENCIES then 1000
when *DIVISIBLE_BY_100 then 100
else 100
end

[solidus_subunit_to_unit, stripe_subunit_to_unit]
end
end
91 changes: 19 additions & 72 deletions app/models/solidus_stripe/gateway.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
# - https://stripe.com/docs/charges/placing-a-hold
module SolidusStripe
class Gateway
include SolidusStripe::MoneyToStripeAmountConverter

def initialize(options)
# Cannot use kwargs because of how the Gateway is initialized by Solidus.
@client = Stripe::StripeClient.new(
Expand Down Expand Up @@ -50,7 +52,7 @@ def capture(_amount_in_cents, _transaction_id, options = {})
raise ArgumentError, "the payment-intent id has the wrong format"
end

payment_intent, _response = client.request { Stripe::PaymentIntent.capture(payment_intent_id) }
payment_intent = request { Stripe::PaymentIntent.capture(payment_intent_id) }

ActiveMerchant::Billing::Response.new(
true, "Capture was successful", { 'stripe_payment_intent' => payment_intent.to_json }, {}
Expand All @@ -74,9 +76,9 @@ def purchase(amount_in_cents, _source, options = {})
currency = options.fetch(:currency)

# Charge the Customer instead of the card:
payment_intent, _response = client.request do
payment_intent = request do
Stripe::PaymentIntent.create({
amount: MoneyToStripeAmountConverter.to_stripe_amount(amount_in_cents, currency),
amount: to_stripe_amount(amount_in_cents, currency),
currency: currency,
**options[:payment_intent_options].to_h
})
Expand All @@ -96,7 +98,7 @@ def purchase(amount_in_cents, _source, options = {})
#
# @return ActiveMerchant::Billing::Response
def void(_transaction_id, source, _options = {})
payment_intent, _response = client.request do
payment_intent = request do
Stripe::PaymentIntent.cancel(source.stripe_payment_intent_id)
end

Expand All @@ -122,84 +124,29 @@ def void(_transaction_id, source, _options = {})
# being performed (required if source is nil)
#
# @return ActiveMerchant::Billing::Response
def credit(amount_in_cents, source, _transaction_id, options = {})
currency = options.fetch(:currency)
source ||= options[:originator].source
def credit(amount_in_cents, _source, _transaction_id, options = {})
refund = options[:originator]
payment = refund.payment
source = payment.source
currency = payment.currency

refund, _response = client.request do
stripe_refund = request do
Stripe::Refund.create(
amount: MoneyToStripeAmountConverter.to_stripe_amount(amount_in_cents, currency),
amount: to_stripe_amount(amount_in_cents, currency),
payment_intent: source.stripe_payment_intent_id,
)
end

ActiveMerchant::Billing::Response.new(
true, "PaymentIntent was refunded successfully", { 'stripe_refund' => refund.to_json }, {}
true, "PaymentIntent was refunded successfully", { 'stripe_refund' => stripe_refund.to_json }, {}
)
end

module MoneyToStripeAmountConverter
extend self

ZERO_DECIMAL_CURRENCIES = %w[
BIF
CLP
DJF
GNF
JPY
KMF
KRW
MGA
PYG
RWF
UGX
VND
VUV
XAF
XOF
XPF
].freeze

THREE_DECIMAL_CURRENCIES = %w[
BHD
JOD
KWD
OMR
TND
].freeze

# special currencies that are represented in cents but
# should be divisible by 100, thus making them integer only.
DIVISIBLE_BY_100 = %w[
HUF
TWD
UGX
].freeze

# Solidus will provide a "fractional" amount, that is specific for each currency
# following the configurationo defined in the Money gem.
#
# Stripe uses the "smallest currency unit",
# (e.g., 100 cents to charge $1.00 or 100 to charge ¥100, a zero-decimal currency)
# https://stripe.com/docs/currencies#zero-decimal
#
# We need to ensure the fractional amount is considering the same number of decimals.
def to_stripe_amount(fractional, currency)
money_subunit_to_unit = ::Money::Currency.new(currency).subunit_to_unit
stripe_subunit_to_unit =
case currency.to_s.upcase
when *ZERO_DECIMAL_CURRENCIES then 1
when *THREE_DECIMAL_CURRENCIES then 1000
when *DIVISIBLE_BY_100 then 100
else 100
end

if stripe_subunit_to_unit == money_subunit_to_unit
fractional
else
(fractional / money_subunit_to_unit.to_d) * stripe_subunit_to_unit
end
end
# Send a request to stripe using the current api keys
# but ignoring the response object.
def request(&block)
result, _response = client.request(&block)
result
end
end
end
Loading