balanced / balanced-api

Balanced API specification.
220 stars 72 forks source link

Push to card specs and scenarios #580

Closed matin closed 10 years ago

matin commented 10 years ago

These are the specifications and scenarios for push to card.

Scenarios still required:

Open issues required to merge:

steveklabnik commented 10 years ago

One thing I've been thinking about this: the Right Way (tm) to do this via hypermedia is to expose the link depending on the actions available, rather than booleans.

Semi-realistic mockup:

This way:

{
    "cards" [{
        "number": "444444444444",
        "can_debit": true,
        "can_credit": true
    }],
    "links": [{
        "cards.debit": "...",
        "cards.credit": "..."
    }]
}

vs

{
    "cards" [{
        "number": "444444444444"
    }],
    "links": [{
        "cards.debit": "..."
    }]
}

and

{
    "cards" [{
        "number": "444444444444"
    }],
    "links": [{
        "cards.debit": "...",
        "cards.credit": "..."
    }]
}

What do you think?

matin commented 10 years ago

@steveklabnik we talked about this before. You're right that this is technically the Right Way to represent this information from a hypermedia API standpoint, but it's not the best experience.

can_debit is also consistent with how we do it for Bank Accounts. Think about the code in each case:

if card.can_credit: ...

vs.

if hasattr(card, credit): ...

or

try card.credit(amount):
    ...
except AttributeError:
    ...

The first way makes more sense—at least to me.

msherry commented 10 years ago

Isn't that just an issue for the clients?

@property
def can_credit(self):
    return hasattr(self.credit)
matin commented 10 years ago

@msherry You make a good point.

Would you do something similar for balanced.js?

Can you create a similar type of abstraction in Java?

steveklabnik commented 10 years ago

You should be able to do in any language.

matin commented 10 years ago

@steveklabnik Shouldn't it be cards.credits (plural) instead of cards.credits? That link, from my understanding, would be used both for a fetch and create.

What would you do if a Bank Account has prior debits but can_debit changes to false?

Also ... should ≠ can

steveklabnik commented 10 years ago

We currently use the singular everywhere already, I think.

Do bank accounts change their ability to debit and be debited? Isn't that a feature of the kind of account?

By "should" I meant "I can't think of a counterexample." Ruby, Python, C++, Java, PHP, C#.... it would be a pretty serious deficiency.

msherry commented 10 years ago

@steveklabnik They do. You can't debit a bank account that hasn't been verified, for instance.

remear commented 10 years ago

Java uses ResourceField to simulate object properties like other languages. I'd really have to do some thinking on how one could be set based on the existence of a link. So, I'll have to go with maybe, but I'm not fond of the idea. Of course, there could be a class method, but then it would be card.can_debit() instead of card.can_debit, which might confuse users.

matin commented 10 years ago

@steveklabnik Do we now ...

from https://docs.balancedpayments.com/1.1/api/cards/#create-a-card-direct

{
    "cards": [
        {
            "address": {
                "city": null, 
                "country_code": null, 
                "line1": null, 
                "line2": null, 
                "postal_code": null, 
                "state": null
            }, 
            "avs_postal_match": null, 
            "avs_result": null, 
            "avs_street_match": null, 
            "brand": "MasterCard", 
            "created_at": "2014-04-29T00:46:42.022131Z", 
            "cvv": "xxx", 
            "cvv_match": "yes", 
            "cvv_result": "Match", 
            "expiration_month": 12, 
            "expiration_year": 2020, 
            "fingerprint": "fc4ccd5de54f42a5e75f76fbfde60948440c7a382ee7d21b2bc509ab9cfed788", 
            "href": "/cards/CC5H1ZZk43sPUu0f9lIxbhgF", 
            "id": "CC5H1ZZk43sPUu0f9lIxbhgF", 
            "is_verified": true, 
            "links": {
                "customer": null
            }, 
            "meta": {}, 
            "name": null, 
            "number": "xxxxxxxxxxxx5100", 
            "updated_at": "2014-04-29T00:46:42.022134Z"
        }
    ], 
    "links": {
        "cards.card_holds": "/cards/{cards.id}/card_holds", 
        "cards.customer": "/customers/{cards.customer}", 
        "cards.debits": "/cards/{cards.id}/debits"
    }
}
steveklabnik commented 10 years ago

@matin Cool! I just mis-remembered. I don't care at all, I was just trying to be consistent.

@remear you construct the object based on the JSON that comes back, right? So why wouldn't you be able to?

steveklabnik commented 10 years ago

@remear

there could be a class method, but then it would be card.can_debit() instead of card.can_debit, which might confuse users.

Ahh, I think this is the difference. Are parenthesis confusing? I don't understand this, but maybe that's just me.

steveklabnik commented 10 years ago

One question about this interface is that can_credit implies that a credit will succeed. It's a statement of possibility, not a guarantee about debit-ability. :/

@msherry @mjallday @mahmoudimus are there scenarios in which can_{debit,credit} transitions from true to false? @msherry , your earlier example was from false to true.

msherry commented 10 years ago

@steveklabnik Sure, if we receive a NOC (Notice of Change) from the RDFI, we can no longer credit/debit that bank account. If a card is lost or stolen, this would likely apply there, as well.

steveklabnik commented 10 years ago

Seems legit.

steveklabnik commented 10 years ago

After exploring all these options, seems like can_debit and can_credit should be there.

matin commented 10 years ago

The scenarios still don't pass, but I think it's largely related to the responses not validating against the schema, which would be fixed with #607.

matin commented 10 years ago

There's still some failing scenarios.

@steveklabnik could you help me debug the following?

undefined method `each_pair' for true:TrueClass (NoMethodError)
./features/step_definitions/http_steps.rb:161:in `checker'
./features/step_definitions/http_steps.rb:167:in `block in checker'
./features/step_definitions/http_steps.rb:161:in `each_pair'
./features/step_definitions/http_steps.rb:161:in `checker'
./features/step_definitions/http_steps.rb:173:in `/^the fields on this (.*) match:$/'
features/rest/push_to_card.feature:16:in `And the fields on this card match:'

undefined method `[]' for nil:NilClass (NoMethodError)
./features/step_definitions/credits.rb:17:in `/^the credit was successfully created$/'
features/rest/push_to_card.feature:99:in `But the credit was successfully created'

and

You can implement step definitions for undefined steps with these snippets:

Then(/^When I GET to \{"(.*?)"=>\[\{"(.*?)"=>"(.*?)", "(.*?)"=>nil, "(.*?)"=>\{"(.*?)"=>nil, "(.*?)"=>"(.*?)", "(.*?)"=>nil\}, "(.*?)"=>"(.*?)", "(.*?)"=>"(.*?)", "(.*?)"=>"(.*?)", "(.*?)"=>nil, "(.*?)"=>"(.*?)", "(.*?)"=>(\d+), "(.*?)"=>nil, "(.*?)"=>\{\}, "(.*?)"=>"(.*?)", "(.*?)"=>"(.*?)", "(.*?)"=>"(.*?)"\}\], "(.*?)"=>\{"(.*?)"=>"(.*?)", "(.*?)"=>"(.*?)", "(.*?)"=>"(.*?)", "(.*?)"=>"(.*?)", "(.*?)"=>"(.*?)"\}\}$/) do |arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19, arg20, arg21, arg22, arg23, arg24, arg25, arg26, arg27, arg28, arg29, arg30, arg31, arg32, arg33, arg34, arg35, arg36, arg37, arg38, arg39|
  pending # express the regexp above with the code you wish you had
end
steveklabnik commented 10 years ago

The second one means you have some of your language wrong, it's not matching any of the steps it knows about. It looks like you're passing in a hash somewhere where it should be a URL.

First means you think you have an array, but you have true.

I'll pull down your branch soon.

steveklabnik commented 10 years ago

Second one fixed, PR sent.

matin commented 10 years ago

@steveklabnik Still getting the following:

undefined method `[]' for nil:NilClass (NoMethodError)
./features/step_definitions/credits.rb:17:in `/^the credit was successfully created$/'
features/rest/push_to_card.feature:99:in `But the credit was successfully created'
steveklabnik commented 10 years ago

@matin that failure is from https://github.com/balanced/balanced-api/issues/591

matin commented 10 years ago

Why does it say push_to_card.feature:99 then in that message?

steveklabnik commented 10 years ago

Because it's the same underlying root cause. If you don't have an empty array in the response, it blows up.

mjallday commented 10 years ago

@steveklabnik please note that according to #616 it will still fail once a fix is deployed for #591

matin commented 10 years ago

Looks like @mjallday just merged a fix for #591. Rerunning the scenarios now.

mjallday commented 10 years ago

@matin still has to be built and go through acceptance. hopefully in an hour or so it will be ready. note my message about amending the specs to allow empty collections.

matin commented 10 years ago

@mjallday Rebased master.

matin commented 10 years ago

Going to merge :smile:. The only failing scenarios are unrelated to push to card.