"show me the money" - upgrading your custom ecommerce application to work with the new...

22
"SHOW ME THE MONEY" - UPGRADING YOUR CUSTOM ECOMMERCE APPLICATION TO WORK WITH THE NEW PAYMENT PROCESSORS David Schlum, Director of IT and Software Architect, Old Town IT Shannon Merritt, Manager, Application Support, AAFP SPECIAL THANKS TO OUR AUG DEVELOPER DEEP DIVE SPONSORS:

Upload: sherilyn-knight

Post on 13-Dec-2015

212 views

Category:

Documents


1 download

TRANSCRIPT

"SHOW ME THE MONEY" - UPGRADING YOUR CUSTOM ECOMMERCE APPLICATION TO WORK WITH THE NEW PAYMENT PROCESSORS

David Schlum, Director of IT and Software Architect, Old Town IT

Shannon Merritt, Manager, Application Support, AAFP

SPECIAL THANKS TO OUR AUG DEVELOPER DEEP DIVE SPONSORS:

Shannon Merrit

Manager, Software DevelopmentAmerican Academy of Family Physicians

• .NET Developer• SQL Developer• Troubleshooter

• oversee development, UX and QA teams• netFORUM developer since 2009• Work closely with Abila on solutions

David Schlum

IT Director & Software ArchitectOld Town IT

• .NET Developer• SQL Developer• Troubleshooter

• netFORUM developer since 2010• Work closely with Abila on solutions

Not “just because”

Why upgrade baseline?

• Lowers PCI compliance concerns – doesn’t eliminate them (yet)• No longer storing encrypted card data in netFORUM

And custom stuff?• You are responsible for upgrading custom tables/processes• Mimicking Abila’s baseline process• This is what we’re here for!

Future?• More in the works

Legacy eCommerce

What’s a legacy custom eCommerce application?

• Custom process• Business need• Still uses the baseline payment processors• Stores payment information in encrypted format**

AAFP’s eCommerce App

Membership Application Process

• Potential members complete membership application• No requirement to login• Pre-authorization only - no actual credit card charge• Association and chapter approval required• AAFP staff review and approve application; create or match application to

netForum individual record• Once approved, membership record created and stored payment info

used for membership payment

Conversion Process

Multi-step approach – Test/Prod

Note: Assuming Payment Processors have already been set up.

• Test• Schema Changes• Metadata (Form) Changes• Create fake encrypted card data• Test conversion with customized Abila Scheduled Task• Clear encrypted card data

• Prod• Deploy schema and metadata• Run conversion• Clear encrypted card data

The Data

Custom table

• v30_cst_key• v30_apm_key• v30_check_amount• v30_name_on_check• v30_check_number• v30_eft_account_number• v30_cc_auth• v30_other_ref_number• v30_cc_security_code• v30_cc_number• v30_cc_number_display• v30_cc_expire• v30_cc_cardholder_name• v30_street• v30_city• v30_state• v30_zip• v30_enc_version• v30_vault_account*• v30_cpi_key**added during migration

ac_customer_payment_info table

• cpi_cst_key• cpi_apm_key

• cpi_name_on_check• cpi_check_number• cpi_eft_account_number,

• cpi_other_preauth_ref_number• cpi_cc_security_code• cpi_cc_number• cpi_cc_number_display• cpi_cc_expire• cpi_cc_cardholder_name• cpi_street• cpi_city• cpi_state• cpi_zip• cpi_enc_version• cpi_vault_account

Fake Encrypted Card Data

UPDATE ac_customer_payment_info SET cpi_cc_number = CASE WHEN apm_type = 'credit card' THEN master.dbo.Encrypt('4111111111111111', LOWER(cpi_key), dbo.av_get_version_number()) ELSE NULL END , cpi_eft_account_number = CASE WHEN apm_type = 'ACH' THEN master.dbo.Encrypt('1111111111', LOWER(cpi_key), dbo.av_get_version_number()) ELSE NULL END , cpi_eft_routing_number = CASE WHEN apm_type = 'ACH' THEN '111111118' ELSE NULL END , cpi_cc_number_display = CASE WHEN apm_type = 'credit card' THEN '41**********1111' ELSE NULL END , cpi_eft_account_number_display = CASE WHEN apm_type = 'ACH' THEN '******1111' ELSE NULL END , cpi_enc_version=2 FROM ac_customer_payment_info JOIN ac_payment_method ON cpi_apm_key = apm_key

Migration Process

Migration Process

• Get records to convert (SPROC)• Create map of old payment method to new payment method• Decrypt credit card data

• For records with customer, map and create new cpi record, add cpi_key to v30 table

• Vault card data and store vault token to v30_vault_account• Remove encypted card data from v30 table

The outcome

PCI Heaven (well, better)

• When customer data is added, cpi record can be created with already vaulted data

• Payments using cpi upon application approval

Lessons Learned

• Simulated conversion saved the day!• ERCİYESEVLER MAH. ŞHT.

YZB.LEVENT ÇETİNKAYA SOK.AKGÖL APT. NO:8/9 KOCASİNAN

• UT8.GetByteCount() != String.Length

• A little extra time on architecture can come in handy too

The solution

public static string StringLengthFix(string p, int length) { if (!string.IsNullOrWhiteSpace(p) && UTF8Encoding.UTF8.GetByteCount(p) > length) { p = StringLengthFix(p.Substring(0, p.Length - 1), length); } return p; }

* Solution Provided to Abila R&D for baseline inclusion

The workaround

public class AAFP_ac_payment : Payment { //Implemented for the Litle Payment Processor only to address encoding issues with special characters. If moving to a //different processor, please remove public AAFP_ac_payment() { base.ElectronicPaymentConfig.PreAuth.RequestMapper.AfterMapLevel1Data += RequestMapper_AfterMapLevel1Data; base.ElectronicPaymentConfig.PostAuth.RequestMapper.AfterMapLevel1Data += RequestMapper_AfterMapLevel1Data; base.ElectronicPaymentConfig.Sale.RequestMapper.AfterMapLevel1Data += RequestMapper_AfterMapLevel1Data; base.ElectronicPaymentConfig.Void.RequestMapper.AfterMapLevel1Data += RequestMapper_AfterMapLevel1Data; } private void RequestMapper_AfterMapLevel1Data(object sender, CreditCardInfoMappingEventArgs e) { ValidateAddressInformation(e.CreditCardInfo); } private void ValidateAddressInformation(CreditCardInfo creditCardInfo) { //values obtained from Litle Processor but we are taking UTF-8 encoding now into account as some unicode characters //are actually being represented by 2 characters when encoded and the Litle service is throwing errors if the address //is too long due to the encoding creditCardInfo.Name = AAFP_Utility.StringLengthFix(creditCardInfo.Name, 100); creditCardInfo.Address = AAFP_Utility.StringLengthFix(creditCardInfo.Address, 35); creditCardInfo.Address2 = AAFP_Utility.StringLengthFix(creditCardInfo.Address2, 35); creditCardInfo.City = AAFP_Utility.StringLengthFix(creditCardInfo.City, 35); creditCardInfo.State = AAFP_Utility.StringLengthFix(creditCardInfo.State, 2); creditCardInfo.Zip = AAFP_Utility.StringLengthFix(creditCardInfo.Zip, 20); creditCardInfo.companyName = AAFP_Utility.StringLengthFix(creditCardInfo.companyName, 40); creditCardInfo.Email = AAFP_Utility.StringLengthFix(creditCardInfo.Email, 100); creditCardInfo.phoneNumber = AAFP_Utility.StringLengthFix(creditCardInfo.phoneNumber, 20); } }

New Interface

Warning! New stuff!

namespace Avectra.netForum.Data { /// <summary> /// An interface used to define the functionality that is required when a class supports electronic payment processing /// through an instance of an ICreditCardProcessor. /// </summary> public interface IProcessesElectronicPayments { /// <summary> /// An object that stores configuration information for use when processing different types of /// electronic payments in netFORUM. /// </summary> ElectronicPaymentConfig ElectronicPaymentConfig { get; set; } /// <summary> /// A method in which ElectronicPaymentTypeConfig and associated CreditCardInfoMapper objects should be /// initialized on the ElectronicPaymentConfig object also provided by the IProcessesElectronicPayments interface. /// </summary> /// <remarks> /// When implementing this interface, this method should be called from a facade's InitializeObjects /// method. Also, it is recommended that you make this method protected virtual, allowing custom /// ElectronicPaymentTypeConfig and CreditCardInfoMapper objects to be provided when needed in order /// to satisfy more complex payment gateway requirements. /// </remarks> void InitializePaymentMappers(); } }

Initialization

public virtual void InitializePaymentMappers() { this.ElectronicPaymentConfig = new ElectronicPaymentConfig( new ElectronicPaymentTypeConfig(new PaymentPreAuthMapper(new CreditCardInfo())), // Pre-Auth new ElectronicPaymentTypeConfig(new PaymentPostAuthMapper(new CreditCardInfo())), // Post-Auth (aka Capture) new ElectronicPaymentTypeConfig(new PaymentSaleMapper(new CreditCardInfo())), // Sale (Check) null, // Credit new ElectronicPaymentTypeConfig(new PaymentVoidMapper(new CreditCardInfo())), // Void null); // Paypal Express }

Mappers

How data gets from FaçadeClass to Payment Processor

Otherspublic CreditCardInfo MapData(ICreditCardProcessor processor, FacadeClass dataSource, params KeyValuePair<string, string>[] additionalData) { // Save our processor reference for use throughout mapping. this.Processor = processor; // Map additional data for use in events and specific mapping routines! AddAdditionalDataForMapping(additionalData); // Spin up our event args object to pass along the CreditCardInfo and data source (FacadeClass) to our events as needed. CreditCardInfoMappingEventArgs args = new CreditCardInfoMappingEventArgs() { CreditCardInfo = this.CreditCardInfo, Processor = this.Processor, DataSource = dataSource, AdditionalData = this.AdditionalData }; // Handle level 1 events and data mapping. OnBeforeMapLevel1Data(args); MapLevel1Data(dataSource); OnAfterMapLevel1Data(args); // Handle level 2 events and data mapping. if (this.Processor.IsLevel2TransactionDataSupportedByProcessor() && this.Processor.IsLevel2TransactionDataSupportedByMerchantAccount) { OnBeforeMapLevel2Data(args); MapLevel2Data(dataSource); OnAfterMapLevel2Data(args); } // Handle level 3 events and data mapping. if (this.Processor.IsLevel3TransactionDataSupportedByProcessor() && this.Processor.IsLevel2TransactionDataSupportedByMerchantAccount && this.Processor.IsLevel3TransactionDataSupportedByMerchantAccount) // Must support level 2 to support level 3... { OnBeforeMapLevel3Data(args); MapLevel3Data(dataSource); OnAfterMapLevel3Data(args); } // Return the CreditCardInfo object that has been populated with netFORUM data based on the mapper configuration/context. return this.CreditCardInfo; }

Payment (and more) events

Payment (and more) events

public class AAFP_ac_payment : Avectra.netForum.Components.AC.Payment { public AAFP_ac_payment() { this.BeforeProcessPostAuthTransaction += AAFP_ac_payment_BeforeProcessPostAuthTransaction; } void AAFP_ac_payment_BeforeProcessPostAuthTransaction(object sender , Avectra.netForum.Data.ProcessElectronicPaymentTransactionEventArgs e) { LogData(e.ElectronicPaymentTransactionData.OrderId, e.ElectronicPaymentTransactionData.Amount); } private void LogData(string p, decimal? nullable) { //TODO:move some electrons } }

Riddle me this, Batman

Any Questions?

Thank you!

David SchlumOld Town [email protected]

Shannon [email protected]