From fe85ea361c411a9b5b7967c3483070e0394209b7 Mon Sep 17 00:00:00 2001 From: Adam Humpherys Date: Tue, 30 Oct 2018 13:20:19 -0600 Subject: [PATCH] Added: EBT Wic (eWic), Gift Card, Fraud Submit. Introduced AvsResultType, which may break current implementations. API implementation complete. --- src/Cardknox.NET/Cardknox.cs | 716 +++++++++++++++++++ src/Cardknox.NET/Cardknox.csproj | 8 +- src/Cardknox.NET/CardknoxRequest.cs | 4 +- src/Cardknox.NET/CardknoxResponse.cs | 4 +- src/Cardknox.NET/Operations/EBTW.cs | 77 ++ src/Cardknox.NET/Operations/Fraud.cs | 44 ++ src/Cardknox.NET/Operations/GiftCard.cs | 28 + src/Cardknox.NET/Operations/OperationBase.cs | 27 + src/Cardknox.NET/ResultType.cs | 63 +- 9 files changed, 954 insertions(+), 17 deletions(-) create mode 100644 src/Cardknox.NET/Operations/EBTW.cs create mode 100644 src/Cardknox.NET/Operations/Fraud.cs create mode 100644 src/Cardknox.NET/Operations/GiftCard.cs diff --git a/src/Cardknox.NET/Cardknox.cs b/src/Cardknox.NET/Cardknox.cs index c53f401..f8e259e 100644 --- a/src/Cardknox.NET/Cardknox.cs +++ b/src/Cardknox.NET/Cardknox.cs @@ -130,6 +130,13 @@ public CardknoxResponse CCSale(CCSale _sale, bool force = false) AddSpecialFields(_sale); + int i = 1; + foreach (string v in _sale.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -208,6 +215,13 @@ public CardknoxResponse CCSave(CCSave _save, bool force = false) if (!IsNullOrWhiteSpace(_save.IP)) _values.Add("xIP", _save.IP); + int i = 1; + foreach (string v in _save.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -255,6 +269,13 @@ public CardknoxResponse CCRefund(CCRefund _refund, bool force = false) if (_refund.CustReceipt) _values.Add("xCustReceipt", _refund.CustReceipt.ToString()); + int i = 1; + foreach (string v in _refund.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -336,6 +357,13 @@ public CardknoxResponse CCAuthOnly(CCAuthOnly _auth, bool force = false) if (_auth.CustReceipt) _values.Add("xCustReceipt", _auth.CustReceipt.ToString()); + int i = 1; + foreach (string v in _auth.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + AddCommonFields(_auth); AddSpecialFields(_auth); @@ -402,6 +430,13 @@ public CardknoxResponse CCCapture(CCCapture _capture, bool force = false) AddSpecialFields(_capture); + int i = 1; + foreach (string v in _capture.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -483,6 +518,13 @@ public CardknoxResponse CCCredit(CCCredit _credit, bool force = false) if (_credit.CustReceipt) _values.Add("xCustReceipt", _credit.CustReceipt.ToString()); + int i = 1; + foreach (string v in _credit.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + AddCommonFields(_credit); AddSpecialFields(_credit); @@ -527,6 +569,13 @@ public CardknoxResponse CCVoid(CCVoid _void, bool force = false) _values.Add("xRefNum", _void.RefNum); // END required information + int i = 1; + foreach (string v in _void.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -582,6 +631,13 @@ public CardknoxResponse CCAdjust(CCAdjust _adjust, bool force = false) if (!IsNullOrWhiteSpace(_adjust.IP)) _values.Add("xIP", _adjust.IP); + int i = 1; + foreach (string v in _adjust.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -636,6 +692,13 @@ public CardknoxResponse CCPostAuth(CCPostAuth _auth, bool force = false) if (!IsNullOrWhiteSpace(_auth.IP)) _values.Add("xIP", _auth.IP); + int i = 1; + foreach (string v in _auth.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + AddCommonFields(_auth); AddSpecialFields(_auth); @@ -680,6 +743,13 @@ public CardknoxResponse CCVoidRefund(CCVoidRefund _refund, bool force = false) _values.Add("xRefNum", _refund.RefNum); // END required information + int i = 1; + foreach (string v in _refund.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -720,6 +790,13 @@ public CardknoxResponse CCVoidRelease(CCVoidRelease _release, bool force = false _values.Add("xRefNum", _release.RefNum); // END required information + int i = 1; + foreach (string v in _release.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -791,6 +868,13 @@ public CardknoxResponse CheckSale(CheckSale _sale, bool force = false) if (_sale.CustReceipt) _values.Add("xCustReceipt", _sale.CustReceipt.ToString()); + int i = 1; + foreach (string v in _sale.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + AddCommonFields(_sale); AddSpecialFields(_sale); @@ -863,6 +947,13 @@ public CardknoxResponse CheckCredit(CheckCredit _credit, bool force = false) if (_credit.CustReceipt) _values.Add("xCustReceipt", _credit.CustReceipt.ToString()); + int i = 1; + foreach (string v in _credit.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + AddCommonFields(_credit); AddSpecialFields(_credit); @@ -924,6 +1015,13 @@ public CardknoxResponse CheckSave(CheckSave _save, bool force = false) if (!IsNullOrWhiteSpace(_save.IP)) _values.Add("xIP", _save.IP); + int i = 1; + foreach (string v in _save.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -967,6 +1065,13 @@ public CardknoxResponse CheckVoid(CheckVoid _void, bool force = false) _values.Add("xRefNum", _void.RefNum); // END required information + int i = 1; + foreach (string v in _void.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -1013,6 +1118,13 @@ public CardknoxResponse CheckRefund(CheckRefund _refund, bool force = false) if (_refund.CustReceipt) _values.Add("xCustReceipt", _refund.CustReceipt.ToString()); + int i = 1; + foreach (string v in _refund.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -1085,6 +1197,13 @@ public CardknoxResponse EBTFSSale(EBTFSSale _sale, bool force = false) if (!IsNullOrWhiteSpace(_sale.IP)) _values.Add("xIP", _sale.IP); + int i = 1; + foreach (string v in _sale.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + AddCommonFields(_sale); AddSpecialFields(_sale); @@ -1158,6 +1277,13 @@ public CardknoxResponse EBTFSCredit(EBTFSCredit _credit, bool force = false) if (!IsNullOrWhiteSpace(_credit.IP)) _values.Add("xIP", _credit.IP); + int i = 1; + foreach (string v in _credit.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + AddCommonFields(_credit); AddSpecialFields(_credit); @@ -1291,6 +1417,13 @@ public CardknoxResponse EBTFSVoucher(EBTFSVoucher _voucher, bool force = false) if (!IsNullOrWhiteSpace(_voucher.IP)) _values.Add("xIP", _voucher.IP); + int i = 1; + foreach (string v in _voucher.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + AddCommonFields(_voucher); AddSpecialFields(_voucher); @@ -1367,6 +1500,13 @@ public CardknoxResponse EBTCBSale(EBTCBSale _sale, bool force = false) if (!IsNullOrWhiteSpace(_sale.IP)) _values.Add("xIP", _sale.IP); + int i = 1; + foreach (string v in _sale.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + AddCommonFields(_sale); AddSpecialFields(_sale); @@ -1440,6 +1580,13 @@ public CardknoxResponse EBTCBCash(EBTCBCash _cash, bool force = false) if (_cash.AllowDuplicate) _values.Add("xAllowDuplicate", _cash.AllowDuplicate.ToString()); + int i = 1; + foreach (string v in _cash.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + if (RequestStarted == null) Log.LogRequest(_values); else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); @@ -1508,6 +1655,575 @@ public CardknoxResponse EBTCBBalance(EBTCBBalance _bal, bool force = false) } #endregion + #region ebt wic + /// + /// The Sale command is used to make a purchase on an EBTW cardholder's cash benefit account. + /// + /// + /// + /// + public CardknoxResponse EBTWSale(EBTWSale _sale, bool force = false) + { + if (_sale.Items.Count == 0) + throw new InvalidOperationException("Must specify items included in this sale."); + if (_sale.Amount == null || _sale.Amount <= 0) + throw new InvalidOperationException("Invalid Amount specified. Amount must be greater than zero."); + if (_values.AllKeys.Length > 4 && !force) + throw new InvalidOperationException("A new instance of Cardknox is required to perform this operation unless 'force' is set to 'true'."); + else if (force) + { + string[] toRemove = _values.AllKeys; + foreach (var v in toRemove) + _values.Remove(v); + _values.Add("xKey", _request._key); + _values.Add("xVersion", _request._cardknoxVersion); + _values.Add("xSoftwareName", _request._software); + _values.Add("xSoftwareVersion", _request._softwareVersion); + } + + // BEGIN required information + _values.Add("xCommand", _sale.Operation); + _values.Add("xAmount", String.Format("{0:N2}", _sale.Amount)); + _values.Add("xDUKPT", _sale.DUKPT); + bool requiredAdded = false; + // These groups are mutually exclusive + if (!IsNullOrWhiteSpace(_sale.CardNum)) + { + _values.Add("xCardNum", _sale.CardNum); + requiredAdded = true; + } + else if (!IsNullOrWhiteSpace(_sale.Token)) + { + _values.Add("xToken", _sale.Token); + requiredAdded = true; + } + else if (!IsNullOrWhiteSpace(_sale.MagStripe)) + { + _values.Add("xMagStripe", _sale.MagStripe); + requiredAdded = true; + } + if (!requiredAdded) + throw new Exception($"Missing required values. Please refer to the API documentation for the {_sale.Operation} operation."); + // END required information + + // Optional, but recommended + if (!IsNullOrWhiteSpace(_sale.Street)) + _values.Add("xStreet", _sale.Street); + + if (!IsNullOrWhiteSpace(_sale.Zip)) + _values.Add("xZip", _sale.Zip); + + if (!IsNullOrWhiteSpace(_sale.IP)) + _values.Add("xIP", _sale.IP); + + int i = 1; + foreach (string v in _sale.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + + if (_sale.Items.Count > 0) + { + i = 1; + foreach (EBTWItem item in _sale.Items) + { + _values.Add($"x{i}UnitPrice", String.Format("{0:N2}", item.UnitPrice)); + _values.Add($"x{i}Qty", item.Qty.ToString()); + _values.Add($"x{i}Upc", item.Upc); + i++; + } + } + + AddCommonFields(_sale); + + AddSpecialFields(_sale); + + if (RequestStarted == null) + Log.LogRequest(_values); + else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); + + var resp = MakeRequest(); + if (RequestCompleted == null) + Log.LogResponse(resp); + else RequestCompleted.Invoke(this, new CardknoxEventArgs(resp)); + + return new CardknoxResponse(resp); + } + + /// + /// The Balance command is used to check the balance on an EBTW cash benefit account. + /// + /// + /// + /// + public CardknoxResponse EBTWBalance(EBTWBalance _bal, bool force = false) + { + if (_values.AllKeys.Length > 4 && !force) + throw new InvalidOperationException("A new instance of Cardknox is required to perform this operation unless 'force' is set to 'true'."); + else if (force) + { + string[] toRemove = _values.AllKeys; + foreach (var v in toRemove) + _values.Remove(v); + _values.Add("xKey", _request._key); + _values.Add("xVersion", _request._cardknoxVersion); + _values.Add("xSoftwareName", _request._software); + _values.Add("xSoftwareVersion", _request._softwareVersion); + } + + // BEGIN required information + _values.Add("xCommand", _bal.Operation); + _values.Add("xDUKPT", _bal.DUKPT); + bool requiredAdded = false; + // These groups are mutually exclusive + if (!IsNullOrWhiteSpace(_bal.CardNum)) + { + _values.Add("xCardNum", _bal.CardNum); + requiredAdded = true; + } + else if (!IsNullOrWhiteSpace(_bal.MagStripe)) + { + _values.Add("xMagStripe", _bal.MagStripe); + requiredAdded = true; + } + if (!requiredAdded) + throw new Exception($"Missing required values. Please refer to the API documentation for the {_bal.Operation} operation."); + // END required information + + // Optional, but recommended + if (!IsNullOrWhiteSpace(_bal.IP)) + _values.Add("xIP", _bal.IP); + + if (RequestStarted == null) + Log.LogRequest(_values); + else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); + + var resp = MakeRequest(); + if (RequestCompleted == null) + Log.LogResponse(resp); + else RequestCompleted.Invoke(this, new CardknoxEventArgs(resp)); + + return new CardknoxResponse(resp); + } + + /// + /// Void EBT Wic (eWic) transaction + /// + /// + /// + /// + public CardknoxResponse EBTWVoid(EBTWVoid _void, bool force = false) + { + if (IsNullOrWhiteSpace(_void.RefNum)) + throw new InvalidOperationException("Invalid RefNum specified. RefNum must reference a previous transaction."); + if (_values.AllKeys.Length > 4 && !force) + throw new InvalidOperationException("A new instance of Cardknox is required to perform this operation unless 'force' is set to 'true'."); + else if (force) + { + string[] toRemove = _values.AllKeys; + foreach (var v in toRemove) + _values.Remove(v); + _values.Add("xKey", _request._key); + _values.Add("xVersion", _request._cardknoxVersion); + _values.Add("xSoftwareName", _request._software); + _values.Add("xSoftwareVersion", _request._softwareVersion); + } + + // BEGIN required information + _values.Add("xCommand", _void.Operation); + _values.Add("xDUKPT", _void.DUKPT); + _values.Add("xRefNum", _void.RefNum); + bool requiredAdded = false; + // These groups are mutually exclusive + if (!IsNullOrWhiteSpace(_void.CardNum)) + { + _values.Add("xCardNum", _void.CardNum); + requiredAdded = true; + } + else if (!IsNullOrWhiteSpace(_void.MagStripe)) + { + _values.Add("xMagStripe", _void.MagStripe); + requiredAdded = true; + } + if (!requiredAdded) + throw new Exception($"Missing required values. Please refer to the API documentation for the {_void.Operation} operation."); + // END required information + + if (RequestStarted == null) + Log.LogRequest(_values); + else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); + + var resp = MakeRequest(); + if (RequestCompleted == null) + Log.LogResponse(resp); + else RequestCompleted.Invoke(this, new CardknoxEventArgs(resp)); + + return new CardknoxResponse(resp); + } + #endregion + + #region gift card + /// + /// The Issue command is used to issue funds to a Cardknox gift card. + /// + /// + /// + /// + public CardknoxResponse GCIssue(GCIssue _issue, bool force = false) + { + if (_issue.Amount == null || _issue.Amount <= 0) + throw new InvalidOperationException("Invalid amount. Sale Amount must be greater than 0."); + if (_values.AllKeys.Length > 4 && !force) + throw new InvalidOperationException("A new instance of Cardknox is required to perform this operation unless 'force' is set to 'true'."); + else if (force) + { + string[] toRemove = _values.AllKeys; + foreach (var v in toRemove) + _values.Remove(v); + _values.Add("xKey", _request._key); + _values.Add("xVersion", _request._cardknoxVersion); + _values.Add("xSoftwareName", _request._software); + _values.Add("xSoftwareVersion", _request._softwareVersion); + } + + // BEGIN required information + _values.Add("xCommand", _issue.Operation); + _values.Add("xAmount", String.Format("{0:N2}", _issue.Amount)); + bool requiredAdded = false; + // These groups are mutually exclusive + if (!IsNullOrWhiteSpace(_issue.CardNum)) + { + _values.Add("xCardNum", _issue.CardNum); + if (!IsNullOrWhiteSpace(_issue.CVV)) + _values.Add("xCVV", _issue.CVV); + if (!IsNullOrWhiteSpace(_issue.Exp)) + _values.Add("xExp", _issue.Exp); + requiredAdded = true; + + if (IsNullOrWhiteSpace(_issue.Exp)) + requiredAdded = false; + } + else if (!IsNullOrWhiteSpace(_issue.Token)) + { + _values.Add("xToken", _issue.Token); + requiredAdded = true; + } + else if (!IsNullOrWhiteSpace(_issue.MagStripe)) + { + _values.Add("xMagStripe", _issue.MagStripe); + requiredAdded = true; + } + if (!requiredAdded) + throw new Exception($"Missing required values. Please refer to the API documentation for the {_issue.Operation} operation."); + // END required information + + // The next many fields are optional and so there will be a lot of if statements here + // Optional, but recommended + if (!IsNullOrWhiteSpace(_issue.Street)) + _values.Add("xStreet", _issue.Street); + + if (!IsNullOrWhiteSpace(_issue.Zip)) + _values.Add("xZip", _issue.Zip); + + // IP is optional, but is highly recommended for fraud detection + if (!IsNullOrWhiteSpace(_issue.IP)) + _values.Add("xIP", _issue.IP); + + if (_issue.CustReceipt) + _values.Add("xCustReceipt", _issue.CustReceipt.ToString()); + + AddCommonFields(_issue); + + AddSpecialFields(_issue); + + int i = 1; + foreach (string v in _issue.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + + if (RequestStarted == null) + Log.LogRequest(_values); + else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); + + var resp = MakeRequest(); + if (RequestCompleted == null) + Log.LogResponse(resp); + else RequestCompleted.Invoke(this, new CardknoxEventArgs(resp)); + + return new CardknoxResponse(resp); + } + public CardknoxResponse GCRedeem(GCRedeem _redeem, bool force = false) + { + if (_redeem.Amount == null || _redeem.Amount <= 0) + throw new InvalidOperationException("Invalid amount. Sale Amount must be greater than 0."); + if (_values.AllKeys.Length > 4 && !force) + throw new InvalidOperationException("A new instance of Cardknox is required to perform this operation unless 'force' is set to 'true'."); + else if (force) + { + string[] toRemove = _values.AllKeys; + foreach (var v in toRemove) + _values.Remove(v); + _values.Add("xKey", _request._key); + _values.Add("xVersion", _request._cardknoxVersion); + _values.Add("xSoftwareName", _request._software); + _values.Add("xSoftwareVersion", _request._softwareVersion); + } + + // BEGIN required information + _values.Add("xCommand", _redeem.Operation); + _values.Add("xAmount", String.Format("{0:N2}", _redeem.Amount)); + bool requiredAdded = false; + // These groups are mutually exclusive + if (!IsNullOrWhiteSpace(_redeem.CardNum)) + { + _values.Add("xCardNum", _redeem.CardNum); + if (!IsNullOrWhiteSpace(_redeem.CVV)) + _values.Add("xCVV", _redeem.CVV); + if (!IsNullOrWhiteSpace(_redeem.Exp)) + _values.Add("xExp", _redeem.Exp); + requiredAdded = true; + + if (IsNullOrWhiteSpace(_redeem.Exp)) + requiredAdded = false; + } + else if (!IsNullOrWhiteSpace(_redeem.Token)) + { + _values.Add("xToken", _redeem.Token); + requiredAdded = true; + } + else if (!IsNullOrWhiteSpace(_redeem.MagStripe)) + { + _values.Add("xMagStripe", _redeem.MagStripe); + requiredAdded = true; + } + if (!requiredAdded) + throw new Exception($"Missing required values. Please refer to the API documentation for the {_redeem.Operation} operation."); + // END required information + + // The next many fields are optional and so there will be a lot of if statements here + // Optional, but recommended + if (!IsNullOrWhiteSpace(_redeem.Street)) + _values.Add("xStreet", _redeem.Street); + + if (!IsNullOrWhiteSpace(_redeem.Zip)) + _values.Add("xZip", _redeem.Zip); + + // IP is optional, but is highly recommended for fraud detection + if (!IsNullOrWhiteSpace(_redeem.IP)) + _values.Add("xIP", _redeem.IP); + + if (_redeem.CustReceipt) + _values.Add("xCustReceipt", _redeem.CustReceipt.ToString()); + + AddCommonFields(_redeem); + + AddSpecialFields(_redeem); + + int i = 1; + foreach (string v in _redeem.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + + if (RequestStarted == null) + Log.LogRequest(_values); + else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); + + var resp = MakeRequest(); + if (RequestCompleted == null) + Log.LogResponse(resp); + else RequestCompleted.Invoke(this, new CardknoxEventArgs(resp)); + + return new CardknoxResponse(resp); + } + + public CardknoxResponse GCBalance(GCBalance _bal, bool force = false) + { + if (_bal.Amount == null || _bal.Amount <= 0) + throw new InvalidOperationException("Invalid amount. Sale Amount must be greater than 0."); + if (_values.AllKeys.Length > 4 && !force) + throw new InvalidOperationException("A new instance of Cardknox is required to perform this operation unless 'force' is set to 'true'."); + else if (force) + { + string[] toRemove = _values.AllKeys; + foreach (var v in toRemove) + _values.Remove(v); + _values.Add("xKey", _request._key); + _values.Add("xVersion", _request._cardknoxVersion); + _values.Add("xSoftwareName", _request._software); + _values.Add("xSoftwareVersion", _request._softwareVersion); + } + + // BEGIN required information + _values.Add("xCommand", _bal.Operation); + _values.Add("xAmount", String.Format("{0:N2}", _bal.Amount)); + bool requiredAdded = false; + // These groups are mutually exclusive + if (!IsNullOrWhiteSpace(_bal.CardNum)) + { + _values.Add("xCardNum", _bal.CardNum); + if (!IsNullOrWhiteSpace(_bal.CVV)) + _values.Add("xCVV", _bal.CVV); + if (!IsNullOrWhiteSpace(_bal.Exp)) + _values.Add("xExp", _bal.Exp); + requiredAdded = true; + + if (IsNullOrWhiteSpace(_bal.Exp)) + requiredAdded = false; + } + else if (!IsNullOrWhiteSpace(_bal.Token)) + { + _values.Add("xToken", _bal.Token); + requiredAdded = true; + } + else if (!IsNullOrWhiteSpace(_bal.MagStripe)) + { + _values.Add("xMagStripe", _bal.MagStripe); + requiredAdded = true; + } + if (!requiredAdded) + throw new Exception($"Missing required values. Please refer to the API documentation for the {_bal.Operation} operation."); + // END required information + + // The next many fields are optional and so there will be a lot of if statements here + // Optional, but recommended + if (!IsNullOrWhiteSpace(_bal.Street)) + _values.Add("xStreet", _bal.Street); + + if (!IsNullOrWhiteSpace(_bal.Zip)) + _values.Add("xZip", _bal.Zip); + + // IP is optional, but is highly recommended for fraud detection + if (!IsNullOrWhiteSpace(_bal.IP)) + _values.Add("xIP", _bal.IP); + + if (_bal.CustReceipt) + _values.Add("xCustReceipt", _bal.CustReceipt.ToString()); + + AddCommonFields(_bal); + + AddSpecialFields(_bal); + + int i = 1; + foreach (string v in _bal.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + + if (RequestStarted == null) + Log.LogRequest(_values); + else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); + + var resp = MakeRequest(); + if (RequestCompleted == null) + Log.LogResponse(resp); + else RequestCompleted.Invoke(this, new CardknoxEventArgs(resp)); + + return new CardknoxResponse(resp); + } + #endregion + + #region fraud + /// + /// The Submit command is used in conjunction with a valid FraudWatch account to submit ecommerce transactions for a fraud verification check. + /// Most fields are required. Refer to API documentation for more information. + /// https://kb.cardknox.com/api/#FRAUD_Fraud_Submit + /// + /// + /// + /// + public CardknoxResponse FraudSubmit(FraudSubmit _submit, bool force = false) + { + if (_submit.Amount == null || _submit.Amount <= 0) + throw new InvalidOperationException("Invalid amount. Sale Amount must be greater than 0."); + if (_values.AllKeys.Length > 4 && !force) + throw new InvalidOperationException("A new instance of Cardknox is required to perform this operation unless 'force' is set to 'true'."); + else if (force) + { + string[] toRemove = _values.AllKeys; + foreach (var v in toRemove) + _values.Remove(v); + _values.Add("xKey", _request._key); + _values.Add("xVersion", _request._cardknoxVersion); + _values.Add("xSoftwareName", _request._software); + _values.Add("xSoftwareVersion", _request._softwareVersion); + } + + // BEGIN required information + _values.Add("xCommand", _submit.Operation); + _values.Add("xAmount", String.Format("{0:N2}", _submit.Amount)); + bool requiredAdded = false; + // These groups are mutually exclusive + if (!IsNullOrWhiteSpace(_submit.CardNum)) + { + _values.Add("xCardNum", _submit.CardNum); + if (!IsNullOrWhiteSpace(_submit.CVV)) + _values.Add("xCVV", _submit.CVV); + if (!IsNullOrWhiteSpace(_submit.Exp)) + _values.Add("xExp", _submit.Exp); + requiredAdded = true; + + if (IsNullOrWhiteSpace(_submit.Exp)) + requiredAdded = false; + } + else if (!IsNullOrWhiteSpace(_submit.Token)) + { + _values.Add("xToken", _submit.Token); + requiredAdded = true; + } + else if (!IsNullOrWhiteSpace(_submit.MagStripe)) + { + _values.Add("xMagStripe", _submit.MagStripe); + requiredAdded = true; + } + if (!requiredAdded) + throw new Exception($"Missing required values. Please refer to the API documentation for the {_submit.Operation} operation."); + // END required information + + // The next many fields are optional and so there will be a lot of if statements here + // Optional, but recommended + if (!IsNullOrWhiteSpace(_submit.Street)) + _values.Add("xStreet", _submit.Street); + + if (!IsNullOrWhiteSpace(_submit.Zip)) + _values.Add("xZip", _submit.Zip); + + // IP is optional, but is highly recommended for fraud detection + if (!IsNullOrWhiteSpace(_submit.IP)) + _values.Add("xIP", _submit.IP); + + if (_submit.CustReceipt) + _values.Add("xCustReceipt", _submit.CustReceipt.ToString()); + + AddCommonFields(_submit); + + AddSpecialFields(_submit); + + int i = 1; + foreach (string v in _submit.CustomFields) + { + _values.Add($"xCustom{i:D2}", v); + i++; + } + + if (RequestStarted == null) + Log.LogRequest(_values); + else RequestStarted.Invoke(this, new CardknoxEventArgs(_values)); + + var resp = MakeRequest(); + if (RequestCompleted == null) + Log.LogResponse(resp); + else RequestCompleted.Invoke(this, new CardknoxEventArgs(resp)); + + return new CardknoxResponse(resp); + } + #endregion + #region private methods private NameValueCollection MakeRequest() { diff --git a/src/Cardknox.NET/Cardknox.csproj b/src/Cardknox.NET/Cardknox.csproj index 274d728..f718110 100644 --- a/src/Cardknox.NET/Cardknox.csproj +++ b/src/Cardknox.NET/Cardknox.csproj @@ -8,16 +8,16 @@ Cardknox.API.Wrapper false CardknoxApi - 3.1.1 + 4.0 https://cardknox.adamh.us/ https://github.com/ahwm/Cardknox-API-Wrapper/blob/master/LICENSE API Wrapper for Cardknox Payment Processor written in C# Refer to https://kb.cardknox.com/api for full API reference. - Maintenance release to fix an issue with logging - cardknox gateway processor payment api + Added: EBT Wic (eWic), Gift Card, Fraud Submit. Introduced AvsResultType, which may break current implementations. API implementation complete. + cardknox gateway processor payment api ebt mastercard payments american express amex jcb diners club giftcard gift card discover © 2018 Adam Humpherys - 3.0.0.0 + 4.0.0.0 diff --git a/src/Cardknox.NET/CardknoxRequest.cs b/src/Cardknox.NET/CardknoxRequest.cs index 3b7f7dd..5c35750 100644 --- a/src/Cardknox.NET/CardknoxRequest.cs +++ b/src/Cardknox.NET/CardknoxRequest.cs @@ -41,9 +41,7 @@ public CardknoxRequest(string key, string software, string softwareVersion, stri /// public static CardknoxRequest BeginRequest(string key, string software, string softwareVersion, string cardknoxVer = null) { - CardknoxRequest r = new CardknoxRequest(key, software, softwareVersion, cardknoxVer); - - return r; + return new CardknoxRequest(key, software, softwareVersion, cardknoxVer); } } } diff --git a/src/Cardknox.NET/CardknoxResponse.cs b/src/Cardknox.NET/CardknoxResponse.cs index 1ead558..c91a2ee 100644 --- a/src/Cardknox.NET/CardknoxResponse.cs +++ b/src/Cardknox.NET/CardknoxResponse.cs @@ -45,7 +45,7 @@ public class CardknoxResponse /// /// /// - public string AvsResultCode { get; } + public AvsResponseType AvsResultCode { get; } /// /// /// @@ -96,7 +96,7 @@ public CardknoxResponse(NameValueCollection _values) if (_values.AllKeys.Contains("xBatch")) Batch = _values["xBatch"]; if (_values.AllKeys.Contains("xAvsResultCode")) - AvsResultCode = _values["xAvsResultCode"]; + AvsResultCode = (AvsResponseType)Enum.Parse(typeof(AvsResponseType), _values["xAvsResultCode"]); if (_values.AllKeys.Contains("xAvsResult")) AvsResult = HttpUtility.UrlDecode(_values["xAvsResult"]); if (_values.AllKeys.Contains("xAuthAmount")) diff --git a/src/Cardknox.NET/Operations/EBTW.cs b/src/Cardknox.NET/Operations/EBTW.cs new file mode 100644 index 0000000..8e362ad --- /dev/null +++ b/src/Cardknox.NET/Operations/EBTW.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace CardknoxApi.Operations +{ + /// + /// The Sale command is used to make a purchase on an EBTW cardholder's cash benefit account. + /// + public class EBTWSale : Sale + { + internal string Operation => "ebtw:sale"; + + /// + /// Items included in the sale transaction operation + /// + public EBTWItems Items { get; set; } + } + /// + /// The Cash Balance enables a cash withdrawal from on an EBTW cardholder's cash benefit account. + /// + public class EBTWBalance : OperationBase + { + internal string Operation => "ebtw:balance"; + } + /// + /// Void a transaction + /// + public class EBTWVoid : OperationBase + { + internal string Operation => "ebtw:void"; + + /// + /// Used to reference a previous transaction when doing a follow-up transaction, typically a refund, void, or capture. (Note: xRefnum can be a 64-bit number and should be stored as BIGINT, Long, Int64 or String) + /// + public string RefNum { get; set; } + } + /// + /// + /// + public class EBTWItems : IEnumerable + { + private List _items = new List(); + + public int Count => _items.Count; + public void Add(EBTWItem item) => _items.Add(item); + + public IEnumerator GetEnumerator() + { + return _items.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } + /// + /// + /// + public class EBTWItem + { + /// + /// Unit price for item specified in xUPC + /// + public decimal UnitPrice { get; set; } + /// + /// Quantity of item specified in xUPC. + /// + public int Qty { get; set; } + /// + /// Universal Product Code. + /// + public string Upc { get; set; } + } +} diff --git a/src/Cardknox.NET/Operations/Fraud.cs b/src/Cardknox.NET/Operations/Fraud.cs new file mode 100644 index 0000000..e02f9a1 --- /dev/null +++ b/src/Cardknox.NET/Operations/Fraud.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CardknoxApi.Operations +{ + /// + /// The Submit command is used in conjunction with a valid FraudWatch account to submit ecommerce transactions for a fraud verification check. + /// + public class FraudSubmit : Sale + { + internal string Operation => "fraud:submit"; + + /// + /// Masked Card number with BIN and last 4 digits exposed + /// + public new string CardNum { get; set; } + + /// + /// Transaction RefNum received from Gateway for FraudWatch verification. + /// + public string GatewayRefNum { get; set; } + /// + /// Transaction status received from gateway for FraudWatch verification. + /// + public StatusType GatewayResult { get; set; } + /// + /// CVV for for FraudWatch verification. (M or N) + /// + public string GatewayCVV { get; set; } + /// + /// Street Address for FraudWatch verification. + /// + public AvsResponseType GatewayAVS { get; set; } + /// + /// Transaction RefNum received from Gateway for FraudWatch verification. + /// + public string GatewayError { get; set; } + /// + /// Specifies if the order origin is Internet OR Phone for FraudWatch verification. + /// + public OrderType OrderType { get; set; } + } +} diff --git a/src/Cardknox.NET/Operations/GiftCard.cs b/src/Cardknox.NET/Operations/GiftCard.cs new file mode 100644 index 0000000..a6c8c27 --- /dev/null +++ b/src/Cardknox.NET/Operations/GiftCard.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CardknoxApi.Operations +{ + /// + /// The Issue command is used to issue funds to a Cardknox gift card. + /// + public class GCIssue : Sale + { + internal string Operation => "gift:issue"; + } + /// + /// The Redeem command is used to debit funds from a Cardknox gift card. + /// + public class GCRedeem : Sale + { + internal string Operation => "gift:redeem"; + } + /// + /// The Balance command is used to check the available balance on a Cardknox gift card. + /// + public class GCBalance : Sale + { + internal string Operation => "gift:balance"; + } +} diff --git a/src/Cardknox.NET/Operations/OperationBase.cs b/src/Cardknox.NET/Operations/OperationBase.cs index 9d56830..dba87f6 100644 --- a/src/Cardknox.NET/Operations/OperationBase.cs +++ b/src/Cardknox.NET/Operations/OperationBase.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Text; @@ -101,5 +102,31 @@ public class OperationBase : Customer /// True/False value indicating if the email address specificied in xEmail should receive a receipt containing the transaction details. (CC/Check operations only) /// public bool CustReceipt { get; set; } = false; + + public CardknoxCustomFields CustomFields { get; set; } = new CardknoxCustomFields(); + } + public class CardknoxCustomFields : IEnumerable + { + private List _fields = new List(); + + public int Count => _fields.Count; + + public void Add(string val) + { + if (_fields.Count >= 20) + throw new InvalidOperationException("A maximum of 20 entries can be specified through custom fields."); + + _fields.Add(val); + } + + public IEnumerator GetEnumerator() + { + return _fields.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } } } diff --git a/src/Cardknox.NET/ResultType.cs b/src/Cardknox.NET/ResultType.cs index f0cdeb2..11aafc8 100644 --- a/src/Cardknox.NET/ResultType.cs +++ b/src/Cardknox.NET/ResultType.cs @@ -45,14 +45,61 @@ public enum StatusType /// public enum CardType { - Unknown, - EBT, - GiftCard, - Amex, - Visa, - MasterCard, - Discover, - Diners, + Unknown, + EBT, + GiftCard, + Amex, + Visa, + MasterCard, + Discover, + Diners, JCB } + /// + /// The Address Verification Service (AVS) response code + /// + public enum AvsResponseType + { + /// + /// Address: Match & 5 Digit Zip: Match + /// + YYY, + /// + /// Address: No Match & 5 Digit Zip: Match + /// + NYZ, + /// + /// Address: Match & 5 Digit Zip: No Match + /// + YNA, + /// + /// Address: No Match & 5 Digit Zip: No Match + /// + NNN, + /// + /// Address Information not verified for domestic transaction + /// + XXU, + /// + /// Address: Match & 9 Digit Zip: Match + /// + YYX, + /// + /// Address: No Match & 9 Digit Zip: Match + /// + NYW, + /// + /// Retry / System Unavailable + /// + XXR, + /// + /// Service Not Supported + /// + XXS + } + public enum OrderType + { + Internet, + Phone + } }