Currently implementing a custom payment method and...
# support
d
Currently implementing a custom payment method and I'm looking for reassurance that I'm on the right track. Using a payment processor, that similar to stripe, requires me to create a transaction which returns an ID that I need to store so I can authorize / capture the payment. The concept of my payment flow with the PSP currently looks like this: 1. Create transaction object through javascript API on checkout payment view 2. Initialize credit card fields with returned transaction id 3. Submit credit card fields to PSP 4. Get success response with transaction hash 5. Update order with transaction hash on a custom controller with which the payment method later authorizes / captures the reserved amount (similar to https://github.com/solidusio/solidus_stripe/blob/a57ff9d21647880f72bcf32b4ad48c09274b3cdd/app/controllers/solidus_stripe/intents_controller.rb) 6. Transition from payment to confirm I guess what I'm unsure about is if I can skip step 5 and somehow submit and store the transaction response hash when transitioning to the confirm checkout step without the custom controller?
k
Where would you store the hash when the application transitions from payment to confirm?
d
The goal is to store the hash on the order. But I'm not sure if this is possible without calling another controller.
k
One thing you could do is adding a new (hidden) field to the payment form and pass the hash along with the checkout's next step request
but that service is called from a controller I guess
d
I see. Thanks.
k
I think v2 doesn't use Intents and doesn't need an extra controller. The Stripe Payment token is retrieved via the Stripe JS SDK (line 59) and injected in the form. If you use the
gateway_payment_profile_id
name, it will be automatically stored on the payment source for that order/user.
d
Ok, will take a closer look at v2 as well.
k
Let us know how it goes!
d
I think the hidden field approach should work if I extend the permitted checkout payment attributes with
response_code
to store the transaction id.
Will do
Another approach might be to just use webhooks and updating the order when receiving the correct event.
k
yes but I guess you are not sure to receive the webhook before the user proceed completing the checkout, isn't it?
d
I need to play around a bit. I think I should be able to somehow stop the user from proceeding to confirm without having an authorized payment. But not sure how yet.
Ok, managed to use
response_code
during transition to submit the transaction_id from the PSP.
Here are the steps I had to do: 1. Add
response_code
param to permitted
checkout_payment_attributes
Copy code
Spree::PermittedAttributes.payment_attributes << :response_code
Spree::PermittedAttributes.class_variable_set(:@@checkout_payment_attributes, [
  payments_attributes: Spree::PermittedAttributes.payment_attributes + [
    source_attributes: Spree::PermittedAttributes.source_attributes
  ]
])
Using
class_variable_set
feels totally wrong but not sure how to otherwise fix this. I thought just extending
payment_attributes
should extend
checkout_payment_attributes
as well, but that one was already initialized. 2. Decorate
Spree::Payment
to use the
response_code
in a before_validation hook to populate `Spree::CreditCard`:
Copy code
module PaymentDecorator
  def self.prepended(base)
    base.before_validation :set_datatrans_source, if: -> { new_record? && source && payment_method&.type == 'Spree::PaymentMethod::DatatransCreditCard' }
    base.before_validation :validate_source, unless: :invalid?
  end

  private

  def set_datatrans_source
    transaction = Datatrans.client.get_transaction(response_code)
    source.gateway_payment_profile_id = transaction['card']['alias']
    source.month = transaction['card']['expiryMonth']
    source.year = transaction['card']['expiryYear']
    source.number = transaction['card']['masked']
    source.name = transaction['card']['info']['brand']
    source.verification_value = true
  end

  Spree::Payment.prepend self
end
The whole before_validation stuff is needed as the authentication endpoint from the PSP is not returning a usable
gateway_payment_profile_id
without having to call the API again with the
response_code
.
Next step is implementing the Gateway logic
@kennyadsl Is it intended behaviour that when there is an already existing
Spree::WalletPaymentSource
that the default
Spree::Payment
object created during transition to the checkout
payment
step gets invalidated when transitioned to
confirm
?
I always end up with two payment records.
Copy code
=> [#<Spree::Payment:0x00007fc4eb5f2460
  id: 51,
  amount: 0.0,
  order_id: 39,
  source_type: "Spree::CreditCard",
  source_id: 16,
  payment_method_id: 6,
  state: "invalid",
  response_code: nil,
  avs_response: nil,
  created_at: Fri, 24 Sep 2021 11:28:21.866170000 UTC +00:00,
  updated_at: Fri, 24 Sep 2021 11:28:27.104091000 UTC +00:00,
  number: "HZKTQLL6",
  cvv_response_code: nil,
  cvv_response_message: nil>,
 #<Spree::Payment:0x00007fc4eb5f2258
  id: 52,
  amount: 0.1825e2,
  order_id: 39,
  source_type: "Spree::CreditCard",
  source_id: 16,
  payment_method_id: 6,
  state: "checkout",
  response_code: nil,
  avs_response: nil,
  created_at: Fri, 24 Sep 2021 11:28:27.081662000 UTC +00:00,
  updated_at: Fri, 24 Sep 2021 11:28:27.081662000 UTC +00:00,
  number: "YTMYFHY5",
  cvv_response_code: nil,
  cvv_response_message: nil>]
k
Not sure I understand exactly your scenario, but this scenario looks familiar
d
I'm basically wondering what the point of creating a
Spree::Payment
object is (
Spree::Order#add_default_payment_from_wallet
) when transitioning to the
payment
step.
The object gets invalidated and replaced by a new one when proceeding to
confirm
with an existing card or a new one.
k
Honestly I can't recall right now but I swear there was a reason 😅
maybe a bit of git blame can help
d
Good call.
7 years old 😅
I can't really find an explanation why this is necessary though. Maybe for checkouts without payment steps where users have predefined payment methods?
k
it refers to this PR on Spree, it's before the fork
d
Thanks. Can you find a reason why creating that payment object is necessary? Maybe I'm too tired 🙈
If not, I will gladly create a fork next week, rip it out, and take a look what breaks.
k
it's not super clear from that PR's context actually and right now I can't dig deeper, sorry
let me know how it goes with the fork, would be great to remove useless logic 🙂
d
Will do. Thanks for investigating 🙂
k
all green?
d
Yes. Removing the state transition only affects the specs in that commit. Everything else still passes.
I guess this would make
Spree::Wallet::DefaultPaymentBuilder
obsolete as well.
Even though
Spree::PaymentCreate
would support updating an existing payment,
Spree::OrderUpdateAttributes#assign_payments_attributes
never reuses a payment (because of the missing
payment
argument).
k
so it creates a new one every time?
d
Yes
Which makes creating a default payment somehow useless. Or at least I haven't found a use for it so far.
k
If we remove that code, do we still have the features described in the specs you removed somewhere else? Maybe on the next checkout step?
d
The result is an invalid payment for every order from a customer with an existing payment source. Not sure how this affects the fraud detection.
Regarding the features in the specs: From what I've learned so far, I don't think they are needed if that code is removed. When you select an existing payment source, a new payment with that source will be created. One thing I'm unsure about though is: can there be a situation where an order from an existing customer can have no billing address when transitioning to the payment step?
k
heading in some meeting, will take a closer look later but I think we can open a PR and start discussing there, WDYT?
d
Sounds good. I'll open one and summarize my findings there for further discussion 👍