-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Worldpay store #3232
Implement Worldpay store #3232
Conversation
(pair ? pair.last : nil) | ||
end | ||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If, in the future, anyone needs to support merchant-scoped tokens, it will be easy to add without breaking compatibility with the current format. Likewise should this form of authorization need to embed the original order id (not something we need to do now, but would be needed to be able to return an authorization from a authorize/purchase call where the caller also wants to store the token in a single call (Worldpay does support this use case)).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think I like this. But I do want to bring up a consideration. Obviously the way AM handles authorization
s is not ideal, but to the extent that there is a standard, that standard seems to be for newly supported elements to be concatenated on the end of whatever is already present, for any action type. So what I would have expected here is that the data for a stored card would start with the second element of a concatenated authorization, regardless of whether that first element actually has a value.
Does that make sense? And if so, does it sound like something we should actually care about?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the assumed advantage of this is that, if we're assuming it needs to remain a string, the ordering allows it to be parsed both by the adapter and by anything else looking at the string (as long as the order is known and maintained), independent of transaction type (some adapters add the action type for further explicit parsing if all/most response elements are present for multiple action types).
I think we could retain backward compatibility with existing pay_as_order
actions by checking the length of a .split
auth.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yah, I think it's a good idea (and easy to implement) to ensure that the order_id, if ever it should be present in a compound auth like this one, would take the first ordinal position (and just be empty for now in store where it doesn't apply). That way any future extensions where both the order_id and these token-specific bits are necessary (such as if anyone needs to implement authorize+store in a single call) can rely on the first element (including if there are no "|" characters) always being the order_id.
Not completely sure though if I follow about checking the length of split for pay-as-order though. Are you referring to how we distinguish between pay-as-order payment method strings and stored token id strings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I think we should go with that.
Not completely sure though if I follow about checking the length of split for pay-as-order though. Are you referring to how we distinguish between pay-as-order payment method strings and stored token id strings?
Yes, though I guess it depends on how the current methods change, we might not even need that. I'll leave it up to you and wait to see the changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@curiousepic Ok, I've pushed up some updates that implement this.
Since we want to ensure there is a slot in our compound authorization strings that can hold the order_id
value (if and when it's used), I went ahead and ensured that these complex authorization strings could be used in place of a bare order_id authorization.
Was actually a bit uncertain whether to proceed that far, but it would have felt awkward to me to have the ability to store the order_ids in an authorization that would have been invalid to use for capture/etc. Especially welcome feedback on this point.
@@ -460,17 +507,35 @@ def handle_response(response) | |||
# - a string or an array of strings (if one of many responses) | |||
# - An array of strings if one of many responses could be considered a | |||
# success. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need the ability to return success based on responses materially different than looking for :ok
in the parsed response. The :last_event
stuff also wouldn't work for store
because the response for store
doesn't include last event information. I broke things down into simpler methods for each type of success-check, and left things more easily extensible for any future types of action-specific success criteria we may need (rather than continue to pile on the existing conditional logic in the original method).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd vote to just axe success_and_message_from
too.
@@ -144,6 +151,10 @@ def credit_request(money, payment_method, options) | |||
commit('credit', build_authorization_request(money, payment_method, options), :ok, 'SENT_FOR_REFUND', options) | |||
end | |||
|
|||
def store_request(credit_card, options) | |||
commit('store', build_store_request(credit_card, options), options) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that we are intentionally not passing the *success_requirements
argument. It is optional, and not applicable to store
. See comments RE: success_and_message_from
.
node.elements.each { |e| parse_element(raw, e) } | ||
else | ||
raw[node.name.underscore.to_sym] = node.text unless node.text.nil? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows us to include the error message when there are newlines. I discovered this issue when trying to make assertions about the returned error message from existing canned responses in the unit tests. Now we can return the error message whether the CDATA
information is preceded and/or followed by newlines or not.
@@ -241,7 +268,7 @@ def add_amount(xml, money, options) | |||
end | |||
|
|||
def add_payment_method(xml, amount, payment_method, options) | |||
if payment_method.is_a?(String) | |||
if options[:payment_type] == :pay_as_order |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have yet to fully grok your additions, but I'm seeing that for users of this feature will need to start sending a new payment_type
option? Would it be best to retain the string check for backward compatibility?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or was this made to distinguish it from stored PMs, since they end up as strings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see now that this is generated internally by a method that parses the PM, neat. Disregard for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, you got it. The problem was that both the TPV tokens and pay-by-reference/pay-as-order string payment methods needed to be distinguished early on. Not a user-facing option, should be fully backwards-compatible.
|
||
def payment_details_from(payment_method) | ||
payment_details = {} | ||
if payment_method.is_a?(CreditCard) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@curiousepic So I updated this to rely on our parsing the complex auth and checking that the token-specific field is in the resultant hash to determine whether it's a pay-by-order auth string or token auth string.
As part of this I flipped the logic around to rely on detecting whether it's a credit card payment method using is_a?
(I generally try really hard to avoid is_a?
). One thing I'm uncertain of is if there is the potential for any other kind of payment_method we need to worry about at this stage which would fail this check but also not be a String (and thus cause the later code to break which assume either a CreditCard
or String
instance being used).
Is there a common method that all card-like objects can respond to which could be used to differentiate them from String
instances without having to resort to is_a?
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! We do often use respond_to?(:number)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I like that better. Updated. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, should be good to go. Nice work on this!
We are only supporting shopper-scoped tokens right now, and not implementing delete/unstore, saving a token along with an authorize or purchase call, overriding token details on use, or setting the token lifetime. The design of the opaque token authorization string we return from `store` has been intentionally designed to be flexible enough to extend in a backwards-compatible way should any of the above features need to be implemented. Unit: 66 tests, 392 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 50 tests, 219 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 96% passed The two failing remote tests are concerning 3DS parameter pass through which the test credentials I'm using does not have permission to use presently. ECS-347 Closes #3232
We are only supporting shopper-scoped tokens right now, and not implementing delete/unstore, saving a token along with an authorize or purchase call, overriding token details on use, or setting the token lifetime. The design of the opaque token authorization string we return from `store` has been intentionally designed to be flexible enough to extend in a backwards-compatible way should any of the above features need to be implemented. Unit: 66 tests, 392 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 100% passed Remote: 50 tests, 219 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications 96% passed The two failing remote tests are concerning 3DS parameter pass through which the test credentials I'm using does not have permission to use presently. ECS-347 Closes activemerchant#3232
We are only supporting shopper-scoped tokens right now, and not
implementing delete/unstore, saving a token along with an authorize or
purchase call, overriding token details on use, or setting the token
lifetime.
The design of the opaque token authorization string we return from
store
has been intentionally designed to be flexible enough to extendin a backwards-compatible way should any of the above features need to
be implemented.
Unit:
59 tests, 329 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
Remote:
50 tests, 219 assertions, 2 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
96% passed
The two failing remote tests are concerning 3DS parameter pass through
which the test credentials I'm using does not have permission to use
presently.
ECS-347