views:

59

answers:

2

I'm using ASP.NET MVC 2.

My current payment types with properties

Keeping it simple, I have three payment types: credit card, e-check, or "bill me later". I want to:

  1. choose one payment type
  2. display some fields for one payment type in my view
  3. run some logic using those fields (specific to the type)
  4. display a confirmation view
  5. run some more logic using those fields (specific to the type)
  6. display a receipt view

Each payment type has fields specific to the type... maybe 2 fields, maybe more. For now, I know how many and what fields, but more could be added. I believe the best thing for my views is to have a partial view per payment type to handle the different fields and let the controller decide which partial to render (if you have a better option, I'm open). My real problem comes from the logic that happens in the controller between views. Each payment type has a variable number of fields. I'd like to keep everything strongly typed, but it feels like some sort of dictionary is the only option. Add to that specific logic that runs depending on the payment type.

In an effort to keep things strongly typed, I've created a class for each payment type. No interface or inherited type since the fields are different per payment type. Then, I've got a Submit() method for each payment type. Then, while the controller is deciding which partial view to display, it also assigns the target of the submit action.

This is not elegant solution and feels very wrong. I'm reaching out for a hand. How would you do this?

A: 

It's one for thing for, say, an Order object to not really care about the particular payment type--just knowing that there is a PledgedAmount and a DepositedAmount property, for example, is enough for that class to do its job (e.g., "don't allow myself to go to Shipped status if order is not charged, via whatever payment method, I don't really care, as long as it's charged somehow I'm cool").

It's another thing for the UI to not really care: you either write a switch to load the right view and be done with it, or you end up creating a dictionary-like collection of fields, rules, and validation metadata and let the UI generate itself dynamically.

For example, I took the latter approach when I was adding international address support for one of my applications. I abstracted addresses into a common set of fields: AdministrativeArea, Municipality, etc, and defined an AddressScheme class which defined AddressFieldRules for each field, stating whether or not it was required, if a regex should be applied, if there is a specific set of allowed values, etc. It was appropriate since we could add or remove countries to the allowed list at any time without recompiling and redeploying the application; the UI could dynamically generate itself based on the AddressScheme, which itself was loaded from the database. Sweet.

But are you really going to be adding payment types (a) with great frequency or (b) without re-compiling your application? Sometimes you really do need an actual Toaster instead of a Breakfast Cooking Machine w/ Bread and Other Yeast-Based Goods Browning Module, so just add a switch or a dictionary that maps a type to the correct view: I think it's perfectly acceptable for the UI to have to be able to distinguish among these types, and, more importantly, for you to custom-write and tailor the presentation of each type--but the higher-up parts might benefit from some abstraction. I talked about that in my answer to a different question related to billing.

In your example, you can share common code for steps 1, 4, and 6, but delegate to specific UI methods and views for steps 2, 3, and 5. And the world will still spin round.

My two cents. Good luck!

Nicholas Piasecki
That's what the other guys on my team are saying. I like the toaster analogy.
Byron Sommardahl
A: 

It sounds like you have three distinct workflows, each pertaining to a different payment method chosen by the user. While you only explicitly indicate steps 3 and 5 as having payment specific concerns, it would seem that everything after step 1 must display payment specific information (unless in step 4 the user isn't confirming payment specific information, and in step 6 their receipt doesn't indicate how they've paid).

In this case, you'll probably create a bigger mess for yourself trying to combine all of this into a single workflow rather than creating distinct controllers for each payment type, factoring out any common behavior for reuse by each of the three paths.

As an aside, while you indicated that you weren't using an interface, the reason you gave seems a little troubling. It sounded like you might have used an interface if the payment types contained the same fields. Interfaces are for defining behavior contracts, so you shouldn't be using them to define common data.

Derek Greer
@Derek: Couldn't the common behavior be IPaymentMethod.MakePayment()?
Byron Sommardahl
If PaymentMethod is where you decided to put your actual payment business logic then yes, but I would argue PaymentMethod probably wouldn't be the proper place for such logic.
Derek Greer