Eric / Brooklyn

Visual

Blog Index

Random 5

Stripe and Tap

Multi-tab Websockets

Ruby vs. Ruby

Cookies

Web Accounts

Emoji Bits

Honeypot Bots

Bad Content

JS Data Chutzpah

Sendgrid Ban

Clean URLs

Git

Concerns

Rails UJS

Extending Devise

ENV Variables

See All

Stripe Connect and Ruby's Tap

June 2020

Location Quirks and DRY Code

Bounce is a platform where people pay to chat with other people. Everything happens in real-time, and is billed on a per-minute basis.

Seems fairly straightforward. We’ll create a Payment Intent (which is Stripe’s newer way of saying charge) after each conversation takes place.

Using Stripe Connect, we can even specify transfer_data — this allows us to transfer money directly from student to teacher.

First Complication

Bounce’s network is global. A teacher in Mexico can help a student in Japan. Luckily, Stripe handles most of the heavy-lifting here.

However, Stripe Connect transactions have varying requirements depending on the location of the teacher. This is probably due to some combination of local regulations, fraud protection, and other things we don’t need to worry about.

So, before we create a Payment Intent, we need to know where the teacher is located — or more specifically, where her bank account is located.

Stripe has this information — and instead of making an API call to Stripe before creating every payment intent (to find the user’s location), we’ll save the location in our database.

Location Differences

From Stripe’s docs:

Since cross-border transfers are not supported in countries outside your region, some accounts will need to use the card payments capability and act as the settlement merchant to process payments.

So, when the teacher’s country differs from the platform country (this is our location, USA), the platform can no longer act as a middle man in the transaction — the teacher becomes the settlement merchant.

This distinction may seem confusing and boring, so we won’t linger here, other than to say that we need to specify something about card payments later on, when the teacher’s country differs from ours.

Payment Intents

Here’s what a generic Payment Intent might look like in Ruby:


pay_int = Stripe::PaymentIntent.create({
			amount: amount,
			customer: student.stripe_student_id,
			currency: 'usd',
			payment_method: customer_payment_method,
			off_session: true,
			confirm: true,
			transfer_data:{
				destination: teacher.stripe_teacher_id,
				amount: transfer_amount
			}
		})

The first few parameters (amount, customer, currency, payment_method) are fairly straightforward.

off_session: true means the student isn’t present in the checkout flow, which is half true for us — technically he might be present, but his saved card is automatically charged when a session ends.

confirm: true means it’s go time, we’re ready to charge.

transfer_data is the crucial parameter — this is how the platform operates, by transferring money from one person to another (minus the platform fee).

Cross border transfer

If a Mexican teacher helps a Japanese student, we need to use the card payments capability. Stripe Connect will automatically enable this for international accounts in the admin (for Express accounts, at least).

As mentioned in the docs, card payments allows teachers to receive funds directly from students, without the platform acting as an intermediary.

If you’re wondering why we don’t treat domestic transfers this way — the reason is that the account setup for a teacher is significantly more of a pain when they need to act as a settlement merchant. If the platform acts as the settlement merchant, things are easier for everyone involved.

Card Payments are already enabled in the admin (for international teachers), but we also have to specify this directly in the Payment Intent. To do this, we include an additional parameter on_behalf_of — this specifies that the teacher will be the settlement merchant for this transaction.

However, we don’t want to have one Stripe::PaymentIntent.create flow for US teachers and another Stripe::PaymentIntent.create for International teachers, because we’d be repeating ourselves and risking a DRY violation.

Ruby’s tap

tap is a ruby method that allows us to … tap into attributes of an object or the keys of a hash.

For example, when creating a new user, we could do something like this:

user = User.new.tap do |u|
  u.username = "horat22"
  u.name = "Horatio"
  u.location = "Earth"
  u.save
end

This mini-bind that we’re in with Stripe presents a great opportunity for tap, since we can conditionally decide whether to add a particular attribute.

And we only want to include the on_behalf_of sometimes, in the case of the cross-border payment mentioned above.

obj = {
	amount: amount,
	customer: student.stripe_student_id,
	currency: 'usd',
	payment_method: customer_payment_method,
	off_session: true,
	confirm: true,
	transfer_data:{
	  destination: teacher.stripe_teacher_id,
	  amount: transfer_amount
	}
}

obj.tap do |hash|
	hash[:on_behalf_of] = teacher.stripe_teacher_id if not_us(teacher.country)
end

pi = Stripe::PaymentIntent.create(obj)

We have a helper method not_us

  def not_us(country)
    country != 'US' && !country.nil?
  end

which returns true if the teacher’s bank account is not located in the USA.

To recap - we first tap into our obj which contains the Payment Intent data. Then we decide whether we want to add :on_behalf_of. Then, we pass obj into Stripe::PaymentIntent.create.

Ruby’s tap has allowed us to maintain a single payment flow, and keep our code DRY.

Conclusion

That’s all for now.