You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by ja...@apache.org on 2026/05/27 15:41:44 UTC

(ofbiz-framework) branch trunk updated: Implemented: Remove legacy ValueLink API integration

This is an automated email from the ASF dual-hosted git repository.

jacopoc pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new b7c56b75f4 Implemented: Remove legacy ValueLink API integration
b7c56b75f4 is described below

commit b7c56b75f48e7392d03fb103f1f5cabb4526af55
Author: Jacopo Cappellato <ja...@gmail.com>
AuthorDate: Thu Apr 30 18:29:43 2026 +0200

    Implemented: Remove legacy ValueLink API integration
---
 .../accounting/config/AccountingUiLabels.xml       |  285 ----
 applications/accounting/config/payment.properties  |   41 +-
 applications/accounting/ofbiz-component.xml        |    1 -
 .../accounting/servicedef/services_valuelink.xml   |  292 ----
 .../thirdparty/valuelink/ValueLinkApi.java         | 1129 ---------------
 .../thirdparty/valuelink/ValueLinkServices.java    | 1470 --------------------
 applications/datamodel/DATAMODEL_CHANGES.adoc      |    1 -
 .../datamodel/data/seed/AccountingSeedData.xml     |    3 -
 .../datamodel/entitydef/accounting-entitymodel.xml |   18 -
 .../ofbiz/order/shoppingcart/CheckOutHelper.java   |   10 -
 .../org/apache/ofbiz/base/util/UtilValidate.java   |   14 +-
 spotbugs/exclude.xml                               |    5 -
 12 files changed, 2 insertions(+), 3267 deletions(-)

diff --git a/applications/accounting/config/AccountingUiLabels.xml b/applications/accounting/config/AccountingUiLabels.xml
index fb301499df..e0a9681eeb 100644
--- a/applications/accounting/config/AccountingUiLabels.xml
+++ b/applications/accounting/config/AccountingUiLabels.xml
@@ -16227,291 +16227,6 @@
         <value xml:lang="zh">使用正则表达式验证样式</value>
         <value xml:lang="zh-TW">使用正則運算式驗證樣式</value>
     </property>
-    <property key="AccountingValueLinkCannotStoreWorkingKey">
-        <value xml:lang="ar">لا يمكن حفظ المفاتيح المحدثة</value>
-        <value xml:lang="de">kann aktualisierte Schlüssel nicht speichern</value>
-        <value xml:lang="en">Unable to store updated keys</value>
-        <value xml:lang="es">No se puede almacenar claves actualizadas</value>
-        <value xml:lang="fr">Impossible de sauvegarder les clés mises à jour</value>
-        <value xml:lang="it">Non è possibile registrare chiavi aggiornate</value>
-        <value xml:lang="ja">更新キーを格納できません</value>
-        <value xml:lang="vi">Hệ thống không thể lưu khóa đã được sửa đổi</value>
-        <value xml:lang="zh">无法存储更新的密钥</value>
-        <value xml:lang="zh-TW">無法儲存更新的金鑰</value>
-    </property>
-    <property key="AccountingValueLinkCannotUpdateWorkingKey">
-        <value xml:lang="ar">لا يمكن تحديث MWK</value>
-        <value xml:lang="de">Kann Schlüssel nicht aktualisieren</value>
-        <value xml:lang="en">Unable to update MWK</value>
-        <value xml:lang="es">No se puede actualizar MWK</value>
-        <value xml:lang="fr">Impossible de mettre à jour le MWK</value>
-        <value xml:lang="it">Non è possibile aggiornare MWK</value>
-        <value xml:lang="ja">MWKを更新できません</value>
-        <value xml:lang="vi">Hệ thống không thể cập nhật MWK</value>
-        <value xml:lang="zh">无法更新商户工作密钥(MWK)</value>
-        <value xml:lang="zh-TW">無法更新商戶工作金鑰(MWK)</value>
-    </property>
-    <property key="AccountingValueLinkFeatureTypeRequested">
-        <value xml:lang="ar">لم يتم العثور على نوع الميزة "نوع" للسلعة: ${productId}</value>
-        <value xml:lang="de">Benötigter Feature Typ TYPE nicht gefunden für Produkt: ${productId}</value>
-        <value xml:lang="en">Required feature type TYPE not found for product: ${productId}</value>
-        <value xml:lang="es">Característica de tipo requerida para el producto no encontrada: ${productId}</value>
-        <value xml:lang="fr">Le type de caractéristique requis n'a pas été trouvé pour l'article ${productId}</value>
-        <value xml:lang="it">Richiesta tipo caratteristica TIPO non trovata per il prodotto: ${productId}</value>
-        <value xml:lang="ja">製品: ${productId} に必要な機能種類TYPEが見つかりませんでした</value>
-        <value xml:lang="vi">Kiểu tính năng (thuộc tính bắt buộc có) không tìm thấy với sản phẩm: ${productId}</value>
-        <value xml:lang="zh">没有找到产品必须的特征类型TYPE:${productId}</value>
-        <value xml:lang="zh-TW">沒有找到產品必須的特色類型TYPE:${productId}</value>
-    </property>
-    <property key="AccountingValueLinkGiftCardActivated">
-        <value xml:lang="ar">اكتمل تفعيل البطاقة المادية.</value>
-        <value xml:lang="de">Aktivierung der Gutscheinkarte ist erfolgt.</value>
-        <value xml:lang="en">Activation of physical card complete.</value>
-        <value xml:lang="es">Activación completa de tarjeta física.</value>
-        <value xml:lang="fr">Activation de la carte complète</value>
-        <value xml:lang="it">Attivazione della carta completata.</value>
-        <value xml:lang="ja">カードのアクティベーションが完了しました。</value>
-        <value xml:lang="vi">Kích hoạt Thẻ vật lý thành công.</value>
-        <value xml:lang="zh">完成卡激活。</value>
-        <value xml:lang="zh-TW">完成卡啟動.</value>
-    </property>
-    <property key="AccountingValueLinkPinDisabled">
-        <value xml:lang="ar">الرمز السري معطل</value>
-        <value xml:lang="de">PIN deaktiviert.</value>
-        <value xml:lang="en">PIN disabled.</value>
-        <value xml:lang="es">PIN deshabilitado.</value>
-        <value xml:lang="fr">PIN désactivé</value>
-        <value xml:lang="it">PIN disabilitato.</value>
-        <value xml:lang="ja">PINが無効になりました。</value>
-        <value xml:lang="vi">Vô hiệu hóa PI.</value>
-        <value xml:lang="zh">禁用了密码。</value>
-        <value xml:lang="zh-TW">禁用了密碼.</value>
-    </property>
-    <property key="AccountingValueLinkPromoCodeInvalid">
-        <value xml:lang="de">Ungültiger Promotion Code im idCode Feld von Feature Typ TYPE</value>
-        <value xml:lang="en">Invalid promo code set on idCode field of feature type TYPE</value>
-        <value xml:lang="es">Código promocional establecido no válido en el campo Código de Identificación del tipo entidad TIPO</value>
-        <value xml:lang="fr">Code promo non valide sur le champs idCode du type de caractéristique TYPE</value>
-        <value xml:lang="it">Codice promozione invalido imposta sul campo idCode del tipo caratteristica TIPO</value>
-        <value xml:lang="ja">機能種類TYPEのIDコード項目に設定されたプロモーションコードが正しくありません</value>
-        <value xml:lang="vi">Mã khuyến mãi không hợp lệ của trường idCode của Kiểu tính năng TYPE</value>
-        <value xml:lang="zh">特征类型TYPE的idCode数据项设置了无效的促销代码</value>
-        <value xml:lang="zh-TW">特色類型TYPE的idCode資料項目設置了不正確促銷識別</value>
-    </property>
-    <property key="AccountingValueLinkReceivedEmptyResponse">
-        <value xml:lang="de">Leere Antwort von ValueLink empfangen</value>
-        <value xml:lang="en">Received back an empty response from ValueLink</value>
-        <value xml:lang="es">Recibido de nuevo una respuesta vacía de ValueLink</value>
-        <value xml:lang="fr">Réception d'une réponse vide depuis ValueLink</value>
-        <value xml:lang="it">Ricevuta indietro una risposta vuota da ValueLink</value>
-        <value xml:lang="ja">ValueLinkから空のレスポンスが戻りました</value>
-        <value xml:lang="vi">Nhận về giá trị phản hồi rỗng từ ValueLink</value>
-        <value xml:lang="zh">从ValueLink收到了一个返回的空响应</value>
-        <value xml:lang="zh-TW">從ValueLink收到了一個返回的空回應</value>
-    </property>
-    <property key="AccountingValueLinkThisTransactionIsNotSupported">
-        <value xml:lang="de">Diese Transaktion wird von ValueLink nicht unterstützt</value>
-        <value xml:lang="en">This transaction is not supported by ValueLink</value>
-        <value xml:lang="es">Esta transacción no es soportada por ValueLink</value>
-        <value xml:lang="fr">Cette transaction n'est pas supportée par ValueLink</value>
-        <value xml:lang="it">Questa transazione non è supportata da ValueLink</value>
-        <value xml:lang="ja">このトランザクションはValueLinkでサポートされていません</value>
-        <value xml:lang="vi">Bút toán này không được hỗ trợ bởi ValueLink</value>
-        <value xml:lang="zh">ValueLink不支持这个交易</value>
-        <value xml:lang="zh-TW">ValueLink不支援這個交易</value>
-    </property>
-    <property key="AccountingValueLinkTransactionFailed">
-        <value xml:lang="ar">فشل العملية برمز الإستجابة: ${responseCode}</value>
-        <value xml:lang="de">Transaktion fehlgeschlagen mit Antwort Code: ${responseCode}</value>
-        <value xml:lang="en">Transaction failed with response code: ${responseCode}</value>
-        <value xml:lang="es">Transacción falla con código de respuesta: ${responseCode}</value>
-        <value xml:lang="fr">Échec de la transaction avec comme réponse : ${responseCode}</value>
-        <value xml:lang="it">Transazione fallita con codice risposta: ${responseCode}</value>
-        <value xml:lang="ja">トランザクションが失敗しました。レスポンスコード: ${responseCode}</value>
-        <value xml:lang="vi">Bút toán không thành công với mã trả về: ${responseCode}</value>
-        <value xml:lang="zh">交易失败,响应代码:${responseCode}</value>
-        <value xml:lang="zh-TW">交易失敗,回應代碼:${responseCode}</value>
-    </property>
-    <property key="AccountingValueLinkTransactionNotValid">
-        <value xml:lang="ar">العملية 0704 غير صحيحة</value>
-        <value xml:lang="de">Keine gültige 0704 Transaktion</value>
-        <value xml:lang="en">Not a valid 0704 transaction</value>
-        <value xml:lang="es">Transacción 0704 no es válida</value>
-        <value xml:lang="fr">N'est pas une transaction 0704 valide</value>
-        <value xml:lang="it">Transazione 0704 non valida</value>
-        <value xml:lang="ja">有効な0704トランザクションではありません</value>
-        <value xml:lang="vi">Không phải là Bút toán 0704 hợp lệ</value>
-        <value xml:lang="zh">不是一个有效的0704交易</value>
-        <value xml:lang="zh-TW">不是一個有效的0704交易</value>
-    </property>
-    <property key="AccountingValueLinkUnableToActivateGiftCard">
-        <value xml:lang="ar">لا يمكن تفعيل بطاقة الهدية</value>
-        <value xml:lang="de">Kann Gutscheinkarte nicht aktivieren</value>
-        <value xml:lang="en">Unable to activate gift card</value>
-        <value xml:lang="es">No se puede activar la gift card</value>
-        <value xml:lang="fr">Impossible d'activer la carte cadeau</value>
-        <value xml:lang="it">Non è possibile attivare la carta regalo</value>
-        <value xml:lang="ja">ギフトカードをアクティベーションできません</value>
-        <value xml:lang="vi">Không thể kích hoạt thẻ quà tặng</value>
-        <value xml:lang="zh">不能激活礼品卡</value>
-        <value xml:lang="zh-TW">不能啟動禮物卡</value>
-    </property>
-    <property key="AccountingValueLinkUnableToCallBalanceInquiry">
-        <value xml:lang="ar">لا يمكن الإتصال باستعلام الرصيد</value>
-        <value xml:lang="de">Kann Saldenabfrage nicht aufrufen</value>
-        <value xml:lang="en">Unable to call balance inquire</value>
-        <value xml:lang="es">Imposible de Equilibrar el Balance</value>
-        <value xml:lang="fr">Impossible d'équilibrer la balance</value>
-        <value xml:lang="it">Non è possibile eseguire l'interrogazione bilancio</value>
-        <value xml:lang="ja">残高照会呼び出しできません</value>
-        <value xml:lang="vi">Không thể thực hiện truy vấn Số dư</value>
-        <value xml:lang="zh">无法调用余额查询</value>
-        <value xml:lang="zh-TW">無法呼叫餘額查詢</value>
-    </property>
-    <property key="AccountingValueLinkUnableToCallHistoryInquiry">
-        <value xml:lang="ar">لا يمكن الإتصال باستعلام السوابق</value>
-        <value xml:lang="de">Kann Historienabfrage nicht aufrufen</value>
-        <value xml:lang="en">Unable to call history inquire</value>
-        <value xml:lang="es">Imposible de Equilibrar el Histórico</value>
-        <value xml:lang="fr">Appel à l'historique impossible</value>
-        <value xml:lang="it">Non è possibile eseguire l'interrogazione dello storico</value>
-        <value xml:lang="ja">履歴照会呼び出しできません</value>
-        <value xml:lang="vi">Không thể thực hiện truy vấn Lịch sử</value>
-        <value xml:lang="zh">无法调用历史记录查询</value>
-        <value xml:lang="zh-TW">無法呼叫歷史記錄查詢</value>
-    </property>
-    <property key="AccountingValueLinkUnableToDisablePin">
-        <value xml:lang="ar">لا يمكن الإتصال بتعطيل الرمز السري</value>
-        <value xml:lang="de">Kann PIN Deaktivierung nicht aufrufen</value>
-        <value xml:lang="en">Unable to call disable pin</value>
-        <value xml:lang="es">Imposible de desactivar el código PIN</value>
-        <value xml:lang="fr">Impossible de désactiver le code PIN</value>
-        <value xml:lang="it">Non è possibile eseguire la disabilitazione del pin</value>
-        <value xml:lang="ja">PIN無効呼び出しできません</value>
-        <value xml:lang="vi">Không thể thực hiện vô hiệu hóa PIN</value>
-        <value xml:lang="zh">无法调用禁用密码</value>
-        <value xml:lang="zh-TW">無法呼叫禁用密碼</value>
-    </property>
-    <property key="AccountingValueLinkUnableToGetFeatureType">
-        <value xml:lang="ar">لا يمكن الوصول لنوع الميزة المطلوبة "ميزة" من السلعة</value>
-        <value xml:lang="de">Kann das benötigte Feature vom Typ TYPE aus dem Produkt nicht ermitteln</value>
-        <value xml:lang="en">Unable to get the required feature type TYPE from Product</value>
-        <value xml:lang="es">Imposible de recuperar el tipo de característica obligatorio para el producto</value>
-        <value xml:lang="fr">Impossible de récupérer le type caractéristique obligatoire pour le produit</value>
-        <value xml:lang="it">Non è possibile ottenete il tipo caratteristica TIPO richiesto da prodotto</value>
-        <value xml:lang="ja">製品から必要な機能種類TYPEを取得できません</value>
-        <value xml:lang="vi">Không thể thu nhận Kiểu tính năng (TYPE) từ Sản phẩm</value>
-        <value xml:lang="zh">从产品无法得到必须的特征类型TYPE</value>
-        <value xml:lang="zh-TW">從產品無法得到必須的特色類型TYPE</value>
-    </property>
-    <property key="AccountingValueLinkUnableToLinkGiftCard">
-        <value xml:lang="ar">لا يمكن ربط بطاقة/بطاقات الهدية</value>
-        <value xml:lang="de">Kann Geschenkgutschein(e) nicht verknüpfen</value>
-        <value xml:lang="en">Unable to link gift card(s)</value>
-        <value xml:lang="es">No se ha podido vincular gift card(s)</value>
-        <value xml:lang="fr">Impossible de lier la carte cadeau</value>
-        <value xml:lang="it">Non è possibile legare la carta(e) regalo</value>
-        <value xml:lang="ja">ギフトカードをリンクできません</value>
-        <value xml:lang="vi">Không thể thu nhận liên kết Thẻ quà tặng</value>
-        <value xml:lang="zh">无法链接到礼品卡</value>
-        <value xml:lang="zh-TW">無法連結到禮物卡</value>
-    </property>
-    <property key="AccountingValueLinkUnableToRedeemGiftCard">
-        <value xml:lang="ar">لا يمكن تخليص بطاقة الهدية</value>
-        <value xml:lang="de">Kann Geschenkgutschein nicht zurücknehmen</value>
-        <value xml:lang="en">Unable to redeem gift card</value>
-        <value xml:lang="es">No es posible canjear gift card</value>
-        <value xml:lang="fr">Impossible de racheter la carte cadeau</value>
-        <value xml:lang="it">Non è possibile ritirare la carta regalo</value>
-        <value xml:lang="ja">ギフトカードを弁済できません</value>
-        <value xml:lang="vi">Không thể thực hiện mua lại Thẻ quà tặng</value>
-        <value xml:lang="zh">无法兑换礼品卡</value>
-        <value xml:lang="zh-TW">無法兌換禮物卡</value>
-    </property>
-    <property key="AccountingValueLinkUnableToRedeemGiftCardFailure">
-        <value xml:lang="ar">فشل تخليص الخدمة</value>
-        <value xml:lang="de">Rücknahme Service fehlgeschlagen</value>
-        <value xml:lang="en">Redeem service failed</value>
-        <value xml:lang="es">Servicio de Canje ha fallado</value>
-        <value xml:lang="fr">Échec dans le rachat de la carte cadeau</value>
-        <value xml:lang="it">Fallito servizio di ritiro</value>
-        <value xml:lang="ja">弁済サービスに失敗しました</value>
-        <value xml:lang="vi">Dịch vụ mua lại không thành công</value>
-        <value xml:lang="zh">兑换服务失败</value>
-        <value xml:lang="zh-TW">兌換服務失敗</value>
-    </property>
-    <property key="AccountingValueLinkUnableToRefundGiftCard">
-        <value xml:lang="ar">فشل رد بطاقة الهدية</value>
-        <value xml:lang="de">Kann Geschenkgutschein nicht rückvergüten</value>
-        <value xml:lang="en">Unable to refund gift card</value>
-        <value xml:lang="es">Imposible reembolsar gift card</value>
-        <value xml:lang="fr">Impossible de rembourser la carte cadeau</value>
-        <value xml:lang="it">Non è possibile rimborsare la carta regalo</value>
-        <value xml:lang="ja">ギフトカードを払出できません</value>
-        <value xml:lang="vi">Không thể Hoàn phí thẻ quà tặng</value>
-        <value xml:lang="zh">无法退款到礼品卡</value>
-        <value xml:lang="zh-TW">無法退款到禮物卡</value>
-    </property>
-    <property key="AccountingValueLinkUnableToRefundGiftCardFailure">
-        <value xml:lang="ar">فشل رد الخدمة</value>
-        <value xml:lang="de">Rückvergütung Service fehlgeschlagen</value>
-        <value xml:lang="en">Refund service failed</value>
-        <value xml:lang="es">Servicio de Reembolso ha fallado</value>
-        <value xml:lang="fr">Échec dans le remboursement de la carte cadeau</value>
-        <value xml:lang="it">Servizio rimborso fallito</value>
-        <value xml:lang="ja">払出サービスに失敗しました</value>
-        <value xml:lang="vi">Dịch vụ Hoàn phí không thành công</value>
-        <value xml:lang="zh">退款服务失败</value>
-        <value xml:lang="zh-TW">退款服務失敗</value>
-    </property>
-    <property key="AccountingValueLinkUnableToRefundGiftCardMaxBalanceExceeded">
-        <value xml:lang="ar">فشل إعادة تحميل بطاقة الهدية: تعدي الرصيد الأعلى: سيتم رد الرسوم</value>
-        <value xml:lang="de">Geschenkgutschein Aufladung fehlgeschlagen: Maximalsaldo überschritten; Zahlungen werden erstattet</value>
-        <value xml:lang="en">Gift Card Reload Failed: Max Balance Exceeded; charges will be refunded</value>
-        <value xml:lang="es">Recarga de Gift Card ha fallado: Excede balance máximo; se reembolsarán los cargos</value>
-        <value xml:lang="fr">Échec dans le rechargement de la carte cadeau : Le plafond a été dépassé. Les frais vous seront remboursés.</value>
-        <value xml:lang="it">Fallita ricarica carta regalo: bilancio massimo oltrepassato; l'importo verrà rimborsato</value>
-        <value xml:lang="ja">ギフトカードのチャージに失敗しました。限度を超えました。チャージは払い戻されます。</value>
-        <value xml:lang="vi">Tải lại thông tin Thẻ quà tặng không thành công vì đã đạt đến hơn hạn mức tối đa của số dư, giá trị tiền đã tính sẽ không bị trừ</value>
-        <value xml:lang="zh">重新载入礼品卡失败:超过了余额上线;将退回充值</value>
-        <value xml:lang="zh-TW">儲值禮物卡失敗:超過了餘額上線;將退回儲值</value>
-    </property>
-    <property key="AccountingValueLinkUnableToReleaseGiftCard">
-        <value xml:lang="ar">فشل محاولة اطلاق دفعة بطاقة الهدية: سجل بطاقة الهدية غير صالح</value>
-        <value xml:lang="de">Geschenkgutscheinzahlung fehlgeschlagen; kein gültiger Geschenkgutschein Datensatz</value>
-        <value xml:lang="en">Attempt to release GiftCard payment failed; not a valid GiftCard record</value>
-        <value xml:lang="es">Intento de liberar el pago de Gift Card fallado; no existe un registro válido de Gift Card</value>
-        <value xml:lang="fr">Le paiement par la carte cadeau à échoué, votre numéro de carte n'est pas valide</value>
-        <value xml:lang="it">Tentativo di rilascio pagamento carta regalo fallito; non c'è un record GiftCard valido</value>
-        <value xml:lang="ja">ギフトカード支払の解除に失敗しました。有効なギフトカード記録ではありません。</value>
-        <value xml:lang="vi">Hệ thống thực hiện giải phóng thanh toán Thẻ tín dụng không thành công do dữ liệu Thẻ tín dụng không đúng</value>
-        <value xml:lang="zh">试图解除礼品卡支付时失败;不是一个有效的礼品卡(GiftCard)记录</value>
-        <value xml:lang="zh-TW">試圖解除禮物卡支付時失敗;不是一個有效的禮物卡(GiftCard)記錄</value>
-    </property>
-    <property key="AccountingValueLinkUnableToReloadGiftCard">
-        <value xml:lang="ar">لا يمكن إعادة تحميل بطاقة الهدية</value>
-        <value xml:lang="de">Kann Geschenkgutschein nicht aufladen</value>
-        <value xml:lang="en">Unable to reload gift card</value>
-        <value xml:lang="es">No se puede cargar la gift card</value>
-        <value xml:lang="fr">Impossible de recharger la carte cadeau</value>
-        <value xml:lang="it">Non è possibile ricaricare la carta regalo</value>
-        <value xml:lang="ja">ギフトカードをチャージできません</value>
-        <value xml:lang="vi">Không thể tải lại thẻ tín dụng</value>
-        <value xml:lang="zh">无法重新载入礼品卡</value>
-        <value xml:lang="zh-TW">無法重新載入禮物卡</value>
-    </property>
-    <property key="AccountingValueLinkUnableToReloadGiftCardFailed">
-        <value xml:lang="ar">فشل إعادة تحميل بطاقة الهدية: خطأ معالجة: سيتم رد الرسوم</value>
-        <value xml:lang="de">Geschenkgutschein aufladen fehlgeschlagen: Ausführungsfehler; Zahlungen werden erstattet</value>
-        <value xml:lang="en">Gift Card Reload Failed: Processing Error; charges will be refunded</value>
-        <value xml:lang="es">Recarga de Gift Card ha fallado: error de procesamiento; se reembolsarán los cargos</value>
-        <value xml:lang="fr">Le rechargement de la carte cadeau a échoué, le frais vous seront remboursés</value>
-        <value xml:lang="it">Fallita ricarica carta regalo: errore durante autorizzazione; l'importo verrà rimborsato</value>
-        <value xml:lang="ja">ギフトカードのチャージに失敗しました。処理エラーです。チャージは払い戻されます。</value>
-        <value xml:lang="vi">Tải lại Thẻ quà tặng không thành công, giá trị tiền đã tính sẽ không bị trừ</value>
-        <value xml:lang="zh">重新载入礼品卡时失败:处理错误;将退回充值</value>
-        <value xml:lang="zh-TW">儲值禮物卡時失敗:處理錯誤;將退回儲值</value>
-    </property>
     <property key="AccountingVarianceReasonGlAccounts">
         <value xml:lang="ar">سبب فوارق حساب GL</value>
         <value xml:lang="de">Varianzgründe Hauptbuch</value>
diff --git a/applications/accounting/config/payment.properties b/applications/accounting/config/payment.properties
index 3bc2970f5a..371e8b55c3 100644
--- a/applications/accounting/config/payment.properties
+++ b/applications/accounting/config/payment.properties
@@ -60,7 +60,7 @@ payment.giftcert.reload.survey.cardNumber=giftCardNumber
 # Survey question (description field) which contains the pin number
 payment.giftcert.reload.survey.pinNumber=giftCardPin
 
-# Gift Certificate type (ofbiz || valuelink)
+# Gift Certificate type (ofbiz)
 payment.giftcert.type=ofbiz
 
 ############################################
@@ -148,45 +148,6 @@ payment.clearcommerce.serverURL=https://test5x.clearcommerce.com:11500
 # Enable Card Verification Methods (CID, CVC, CVV2)
 payment.clearcommerce.enableCVM=N
 
-############################################
-# ValueLink Configuration
-############################################
-
-# Full merchant ID (MMMMMMMMMMM)
-payment.valuelink.merchantId=[MerchantID]
-
-# Terminal ID (should be different for each application server starting at 0001)
-payment.valuelink.terminalId=0001
-
-# Alternate merchant ID (i.e. store ID, etc)
-payment.valuelink.altMerchantId=999
-
-# Modes (SVDotRaw, Debug, etc) delimited by '/'
-payment.valuelink.modes=Debug/SVDotRaw
-
-# URL to use to communicate with ValueLink
-payment.valuelink.url=https://www.callit.com/vltest/api1.asp
-
-# Connection timeout in seconds
-payment.valuelink.timeout=34
-
-# Debug mode dumps all request/response info to the console (Y|N)
-payment.valuelink.debug=Y
-
-# Certificate alias to pass for client auth
-payment.valuelink.certificateAlias=
-
-# ValueLink Timestamp Format
-payment.valuelink.timestamp=ddMMyyyyHHmmss
-
-# Prime Modulus (P) for key generation (should be a hex string will be converted to unsigned BigInteger from byte array)
-payment.valuelink.prime=
-
-# Generator (G) for key generation (should be smaller then prime, so this should be in the form for a integer)
-payment.valuelink.generator=
-
-# ValueLink Public Value (128byte as Hex String)
-payment.valuelink.publicValue=
 
 ############################################
 # VeriSign PayFlow Pro Configuration
diff --git a/applications/accounting/ofbiz-component.xml b/applications/accounting/ofbiz-component.xml
index 497dea31f0..5f305165e3 100644
--- a/applications/accounting/ofbiz-component.xml
+++ b/applications/accounting/ofbiz-component.xml
@@ -57,7 +57,6 @@ under the License.
     <service-resource type="model" loader="main" location="servicedef/services_paypal.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_pccharge.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_rita.xml"/>
-    <service-resource type="model" loader="main" location="servicedef/services_valuelink.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_verisign.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_orbital.xml"/>
     <service-resource type="model" loader="main" location="servicedef/services_securepay.xml"/>
diff --git a/applications/accounting/servicedef/services_valuelink.xml b/applications/accounting/servicedef/services_valuelink.xml
deleted file mode 100644
index 655e59e5a5..0000000000
--- a/applications/accounting/servicedef/services_valuelink.xml
+++ /dev/null
@@ -1,292 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-Licensed to the Apache Software Foundation (ASF) under one
-or more contributor license agreements.  See the NOTICE file
-distributed with this work for additional information
-regarding copyright ownership.  The ASF licenses this file
-to you under the Apache License, Version 2.0 (the
-"License"); you may not use this file except in compliance
-with the License.  You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing,
-software distributed under the License is distributed on an
-"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-KIND, either express or implied.  See the License for the
-specific language governing permissions and limitations
-under the License.
--->
-
-<services xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xsi:noNamespaceSchemaLocation="https://ofbiz.apache.org/dtds/services.xsd">
-    <description>Accounting Services</description>
-    <vendor>OFBiz</vendor>
-    <version>1.0</version>
-
-    <!-- ValueLink Implementations -->
-    <service name="valueLinkProcessor" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="giftCardProcessor" auth="true">
-        <description>Gift Card Payment Processing</description>
-        <implements service="giftCardProcessInterface"/>
-    </service>
-    <service name="valueLinkRelease" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="giftCardRelease" auth="true">
-        <description>Gift Card Release (reverse) Payment</description>
-        <implements service="paymentReleaseInterface"/>
-    </service>
-    <service name="valueLinkRefund" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="giftCardRefund" auth="true">
-        <description>Gift Card Refund Payment</description>
-        <implements service="paymentRefundInterface"/>
-    </service>
-
-    <!-- ValueLink Fulfillment Services -->
-    <service name="valueLinkGcPurchase" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="giftCardPurchase" auth="true">
-        <description>Gift Card Payment Processing</description>
-        <implements service="itemFulfillmentInterface"/>
-    </service>
-    <service name="valueLinkGcReload" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="giftCardReload" auth="true">
-        <description>Gift Card Payment Processing</description>
-        <implements service="itemFulfillmentInterface"/>
-    </service>
-
-    <!-- ValueLink Transaction Services -->
-    <service name="createVLKeys" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="createKeys" auth="true">
-        <description>Create Public/Private Keys + KEK and Display Them</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="kekOnly" type="Boolean" mode="IN" optional="true"/>
-        <attribute name="kekTest" type="String" mode="IN" optional="true"/>
-        <attribute name="output" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="testKekEncryption" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="testKekEncryption" auth="true">
-        <description>Test KEK Encryption</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="kekTest" type="String" mode="IN" optional="false"/>
-        <attribute name="mode" type="Integer" mode="IN" optional="false"/>
-        <attribute name="output" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="assignWorkingKey" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="assignWorkingKey" auth="true">
-        <description>Assign a new working key to ValueLink</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="desHexString" type="String" mode="IN" optional="true"/>
-    </service>
-    <service name="activateGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="activate" auth="true">
-        <description>Activate (create new) gift card</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="vlPromoCode" type="String" mode="IN" optional="false"/>
-        <attribute name="currency" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="amount" type="BigDecimal" mode="INOUT" optional="false"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="authCode" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardNumber" type="String" mode="OUT" optional="true"/>
-        <attribute name="pin" type="String" mode="OUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="voidActivateGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="voidActivate" auth="true">
-        <description>Void a Activate (create new) gift card</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="currency" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="amount" type="BigDecimal" mode="INOUT" optional="false"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="authCode" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardNumber" type="String" mode="INOUT" optional="true"/>
-        <attribute name="pin" type="String" mode="INOUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="redeemGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="redeem" auth="true">
-        <description>Redeem (take money off) gift card</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="cardNumber" type="String" mode="IN" optional="false"/>
-        <attribute name="pin" type="String" mode="IN" optional="false"/>
-        <attribute name="currency" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="amount" type="BigDecimal" mode="INOUT" optional="false"/>
-        <attribute name="previousAmount" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="authCode" type="String" mode="OUT" optional="true"/>
-        <attribute name="cashBack" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="voidRedeemGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="voidRedeem" auth="true">
-        <description>Void a Redeem (take money off) gift card</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="cardNumber" type="String" mode="IN" optional="false"/>
-        <attribute name="pin" type="String" mode="IN" optional="false"/>
-        <attribute name="currency" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="amount" type="BigDecimal" mode="INOUT" optional="false"/>
-        <attribute name="previousAmount" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="authCode" type="String" mode="OUT" optional="true"/>
-        <attribute name="cashBack" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="reloadGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="reload" auth="true">
-        <description>Reload (add money to) gift card</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="cardNumber" type="String" mode="IN" optional="false"/>
-        <attribute name="pin" type="String" mode="IN" optional="false"/>
-        <attribute name="currency" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="amount" type="BigDecimal" mode="INOUT" optional="false"/>
-        <attribute name="previousAmount" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="authCode" type="String" mode="OUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="voidReloadGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="voidReload" auth="true">
-        <description>Void a Reload (add money to) gift card</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="cardNumber" type="String" mode="IN" optional="false"/>
-        <attribute name="pin" type="String" mode="IN" optional="false"/>
-        <attribute name="currency" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="amount" type="BigDecimal" mode="INOUT" optional="false"/>
-        <attribute name="previousAmount" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="authCode" type="String" mode="OUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="balanceInquireGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="balanceInquire" auth="false">
-        <description>Inquire current card balance</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="cardNumber" type="String" mode="IN" optional="false"/>
-        <attribute name="pin" type="String" mode="IN" optional="false"/>
-        <attribute name="currency" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="balance" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="transactionHistoryGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="transactionHistory" auth="false">
-        <description>Obtain card transaction history</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="cardNumber" type="String" mode="IN" optional="false"/>
-        <attribute name="pin" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="balance" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="history" type="List" mode="OUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="refundGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="refund" auth="true">
-        <description>Refund a gift card</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="cardNumber" type="String" mode="IN" optional="false"/>
-        <attribute name="pin" type="String" mode="IN" optional="false"/>
-        <attribute name="currency" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="amount" type="BigDecimal" mode="INOUT" optional="false"/>
-        <attribute name="previousAmount" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="authCode" type="String" mode="OUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="voidRefundGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="voidRefund" auth="true">
-        <description>Refund a gift card</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="cardNumber" type="String" mode="IN" optional="false"/>
-        <attribute name="pin" type="String" mode="IN" optional="false"/>
-        <attribute name="currency" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="amount" type="BigDecimal" mode="INOUT" optional="false"/>
-        <attribute name="previousAmount" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="authCode" type="String" mode="OUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="linkPhysicalGiftCard" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="linkPhysicalCard" auth="false">
-        <description>Link a physical card to a virtual account</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="virtualCard" type="String" mode="IN" optional="false"/>
-        <attribute name="virtualPin" type="String" mode="IN" optional="false"/>
-        <attribute name="physicalCard" type="String" mode="IN" optional="false"/>
-        <attribute name="physicalPin" type="String" mode="IN" optional="false"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="authCode" type="String" mode="OUT" optional="true"/>
-        <attribute name="amount" type="BigDecimal" mode="OUT" optional="false"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="disableGiftCardPin" engine="java"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="disablePin" auth="true">
-        <description>Disable a gift card PIN</description>
-        <attribute name="paymentConfig" type="String" mode="IN" optional="false"/>
-        <attribute name="cardNumber" type="String" mode="IN" optional="false"/>
-        <attribute name="pin" type="String" mode="IN" optional="false"/>
-        <attribute name="orderId" type="String" mode="IN" optional="true"/>
-        <attribute name="partyId" type="String" mode="IN" optional="true"/>
-        <attribute name="processResult" type="Boolean" mode="OUT" optional="false"/>
-        <attribute name="responseCode" type="String" mode="OUT" optional="false"/>
-        <attribute name="balance" type="BigDecimal" mode="OUT" optional="true"/>
-        <attribute name="expireDate" type="String" mode="OUT" optional="true"/>
-        <attribute name="cardClass" type="String" mode="OUT" optional="true"/>
-        <attribute name="referenceNum" type="String" mode="OUT" optional="false"/>
-    </service>
-    <service name="vlTimeOutReversal" engine="java" validate="false"
-            location="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkServices" invoke="timeOutReversal" auth="true">
-        <description></description>
-    </service>
-</services>
diff --git a/applications/accounting/src/main/java/org/apache/ofbiz/accounting/thirdparty/valuelink/ValueLinkApi.java b/applications/accounting/src/main/java/org/apache/ofbiz/accounting/thirdparty/valuelink/ValueLinkApi.java
deleted file mode 100644
index d9fcf326d5..0000000000
--- a/applications/accounting/src/main/java/org/apache/ofbiz/accounting/thirdparty/valuelink/ValueLinkApi.java
+++ /dev/null
@@ -1,1129 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.accounting.thirdparty.valuelink;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PrivateKey;
-import java.security.PublicKey;
-import java.security.SecureRandom;
-import java.security.spec.InvalidKeySpecException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyAgreement;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.interfaces.DHPrivateKey;
-import javax.crypto.interfaces.DHPublicKey;
-import javax.crypto.spec.DESKeySpec;
-import javax.crypto.spec.DESedeKeySpec;
-import javax.crypto.spec.DHParameterSpec;
-import javax.crypto.spec.DHPrivateKeySpec;
-import javax.crypto.spec.DHPublicKeySpec;
-import javax.crypto.spec.IvParameterSpec;
-
-import org.apache.commons.codec.binary.Base64;
-import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.GeneralException;
-import org.apache.ofbiz.base.util.HttpClient;
-import org.apache.ofbiz.base.util.HttpClientException;
-import org.apache.ofbiz.base.util.StringUtil;
-import org.apache.ofbiz.base.util.UtilValidate;
-import org.apache.ofbiz.entity.Delegator;
-import org.apache.ofbiz.entity.GenericEntityException;
-import org.apache.ofbiz.entity.GenericValue;
-import org.apache.ofbiz.entity.util.EntityQuery;
-
-/**
- * ValueLinkApi - Implementation of ValueLink Encryption and Transport
- */
-public class ValueLinkApi {
-
-    private static final String MODULE = ValueLinkApi.class.getName();
-
-    // static object cache
-    private static Map<String, Object> objectCache = new HashMap<>();
-
-    // instance variables
-    private Delegator delegator = null;
-    private Properties props = null;
-    private SecretKey kek = null;
-    private SecretKey mwk = null;
-    private String merchantId = null;
-    private String terminalId = null;
-    private Long mwkIndex = null;
-    private boolean debug = false;
-
-    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
-
-    protected ValueLinkApi() { }
-    protected ValueLinkApi(Delegator delegator, Properties props) {
-        String mId = (String) props.get("payment.valuelink.merchantId");
-        String tId = (String) props.get("payment.valuelink.terminalId");
-        this.delegator = delegator;
-        this.merchantId = mId;
-        this.terminalId = tId;
-        this.props = props;
-        if ("Y".equalsIgnoreCase((String) props.get("payment.valuelink.debug"))) {
-            this.debug = true;
-        }
-
-        if (debug) {
-            Debug.logInfo("New ValueLinkApi instance created", MODULE);
-            Debug.logInfo("Merchant ID : " + merchantId, MODULE);
-            Debug.logInfo("Terminal ID : " + terminalId, MODULE);
-        }
-    }
-
-    /**
-     * Obtain an instance of the ValueLinkApi
-     * @param delegator Delegator used to query the encryption keys
-     * @param props Properties to use for the Api (usually payment.properties)
-     * @param reload When true, will replace an existing instance in the cache and reload all properties
-     * @return ValueLinkApi reference
-     */
-    public static ValueLinkApi getInstance(Delegator delegator, Properties props, boolean reload) {
-        if (props == null) {
-            throw new IllegalArgumentException("Properties cannot be null");
-        }
-        String merchantId = (String) props.get("payment.valuelink.merchantId");
-
-        ValueLinkApi api = (ValueLinkApi) objectCache.get(merchantId);
-        if (api == null) {
-            throw new RuntimeException("Runtime problems with ValueLinkApi; unable to create instance");
-        }
-        if (reload) {
-            synchronized (ValueLinkApi.class) {
-                api = (ValueLinkApi) objectCache.get(merchantId);
-                if (api == null) {
-                    api = new ValueLinkApi(delegator, props);
-                    objectCache.put(merchantId, api);
-                }
-            }
-        }
-
-        return api;
-    }
-
-    /**
-     * Obtain an instance of the ValueLinkApi; this method will always return an existing reference if one is available
-     * @param delegator Delegator used to query the encryption keys
-     * @param props Properties to use for the Api (usually payment.properties)
-     * @return Obtain an instance of the ValueLinkApi
-     */
-    public static ValueLinkApi getInstance(Delegator delegator, Properties props) {
-        return getInstance(delegator, props, false);
-    }
-
-    /**
-     * Encrypt the defined pin using the configured keys
-     * @param pin Plain text String of the pin
-     * @return Hex String of the encrypted pin (EAN) for transmission to ValueLink
-     */
-    public String encryptPin(String pin) {
-        byte[] rawIv = new byte[8];
-        SECURE_RANDOM.nextBytes(rawIv);
-        IvParameterSpec iv = new IvParameterSpec(rawIv);
-        // get the Cipher
-        Cipher mwkCipher = null;
-        try {
-            mwkCipher = getCipher(this.getMwkKey(), Cipher.ENCRYPT_MODE, iv);
-        } catch (GeneralException e1) {
-            Debug.logError(e1, MODULE);
-        }
-
-        // pin to bytes
-        byte[] pinBytes = pin.getBytes(StandardCharsets.UTF_8);
-
-        // 7 bytes of random data
-        byte[] randomBytes = this.getRandomBytes(7);
-
-        // pin checksum
-        byte[] checkSum = this.getPinCheckSum(pinBytes);
-
-        // put all together
-        byte[] eanBlock = new byte[16];
-        int i;
-        for (i = 0; i < randomBytes.length; i++) {
-            eanBlock[i] = randomBytes[i];
-        }
-        eanBlock[7] = checkSum[0];
-        for (i = 0; i < pinBytes.length; i++) {
-            eanBlock[i + 8] = pinBytes[i];
-        }
-
-        // encrypy the ean
-        String encryptedEanHex = null;
-        try {
-            byte[] encryptedEan = mwkCipher.doFinal(eanBlock);
-            encryptedEanHex = StringUtil.toHexString(encryptedEan);
-        } catch (IllegalStateException | BadPaddingException | IllegalBlockSizeException e) {
-            Debug.logError(e, MODULE);
-        }
-
-        if (debug) {
-            Debug.logInfo("encryptPin : " + pin + " / " + encryptedEanHex, MODULE);
-        }
-
-        return encryptedEanHex;
-    }
-
-    /**
-     * Decrypt an encrypted pin using the configured keys
-     * @param pin Hex String of the encrypted pin (EAN)
-     * @return Plain text String of the pin @
-     */
-    public String decryptPin(String pin) {
-        // separate prefix with IV from the rest of encrypted data
-        byte[] encryptedPayload = Base64.decodeBase64(pin.getBytes());
-        byte[] iv = new byte[8];
-        byte[] encryptedBytes = new byte[encryptedPayload.length - iv.length];
-
-        // populate iv with bytes:
-        System.arraycopy(encryptedPayload, 0, iv, 0, iv.length);
-
-        // populate encryptedBytes with bytes:
-        System.arraycopy(encryptedPayload, iv.length, encryptedBytes, 0, encryptedBytes.length);
-
-        Cipher mwkCipher = null;
-        try {
-            mwkCipher = getCipher(getMwkKey(), Cipher.ENCRYPT_MODE, new IvParameterSpec(iv));
-        } catch (GeneralException e1) {
-            Debug.logError(e1, MODULE);
-        }
-
-        // decrypt pin
-        String decryptedPinString = null;
-        try {
-            byte[] decryptedEan = mwkCipher.doFinal(StringUtil.fromHexString(pin));
-            byte[] decryptedPin = getByteRange(decryptedEan, 8, 8);
-            decryptedPinString = new String(decryptedPin, StandardCharsets.UTF_8);
-        } catch (IllegalStateException | BadPaddingException | IllegalBlockSizeException e) {
-            Debug.logError(e, MODULE);
-        }
-
-        if (debug) {
-            Debug.logInfo("decryptPin : " + pin + " / " + decryptedPinString, MODULE);
-        }
-
-        return decryptedPinString;
-    }
-
-    /**
-     * Transmit a request to ValueLink
-     * @param request Map of request parameters
-     * @return Map of response parameters
-     * @throws HttpClientException
-     */
-    public Map<String, Object> send(Map<String, Object> request) throws HttpClientException {
-        return send((String) props.get("payment.valuelink.url"), request);
-    }
-
-    /**
-     * Transmit a request to ValueLink
-     * @param url override URL from what is defined in the properties
-     * @param request request Map of request parameters
-     * @return Map of response parameters
-     * @throws HttpClientException
-     */
-    public Map<String, Object> send(String url, Map<String, Object> request) throws HttpClientException {
-        if (debug) {
-            Debug.logInfo("Request : " + url + " / " + request, MODULE);
-        }
-
-        // read the timeout value
-        String timeoutString = (String) props.get("payment.valuelink.timeout");
-        int timeout = 34;
-        try {
-            timeout = Integer.parseInt(timeoutString);
-        } catch (NumberFormatException e) {
-            Debug.logError(e, "Unable to set timeout to " + timeoutString + " using default " + timeout);
-        }
-
-        // create the HTTP client
-        HttpClient client = new HttpClient(url, request);
-        client.setTimeout(timeout * 1000);
-        client.setDebug(debug);
-
-        client.setClientCertificateAlias((String) props.get("payment.valuelink.certificateAlias"));
-        String response = client.post();
-
-        // parse the response and return a map
-        return this.parseResponse(response);
-    }
-
-    /**
-     * Output the creation of public/private keys + KEK to the console for manual database update @
-     */
-    public StringBuffer outputKeyCreation(boolean kekOnly, String kekTest) {
-        return this.outputKeyCreation(0, kekOnly, kekTest);
-    }
-
-    private StringBuffer outputKeyCreation(int loop, boolean kekOnly, String kekTest) {
-        StringBuffer buf = new StringBuffer();
-        loop++;
-
-        if (loop > 100) {
-            // only loop 100 times; then throw an exception
-            throw new IllegalStateException("Unable to create 128 byte keys in 100 tries");
-        }
-
-        // place holder for the keys
-        DHPrivateKey privateKey = null;
-        DHPublicKey publicKey = null;
-
-        if (!kekOnly) {
-            KeyPair keyPair = null;
-            try {
-                keyPair = this.createKeys();
-            } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidAlgorithmParameterException e) {
-                Debug.logError(e, MODULE);
-            }
-
-            if (keyPair != null) {
-                publicKey = (DHPublicKey) keyPair.getPublic();
-                privateKey = (DHPrivateKey) keyPair.getPrivate();
-
-                if (publicKey == null || publicKey.getY().toByteArray().length != 128) {
-                    // run again until we get a 128 byte public key for VL
-                    return this.outputKeyCreation(loop, kekOnly, kekTest);
-                }
-            } else {
-                Debug.logInfo("Returned a null KeyPair", MODULE);
-                return this.outputKeyCreation(loop, kekOnly, kekTest);
-            }
-        } else {
-            // use our existing private key to generate a KEK
-            try {
-                privateKey = (DHPrivateKey) this.getPrivateKey();
-            } catch (Exception e) {
-                Debug.logError(e, MODULE);
-            }
-        }
-
-        // the KEK
-        byte[] kekBytes = null;
-        try {
-            kekBytes = this.generateKek(privateKey);
-        } catch (NoSuchAlgorithmException | InvalidKeyException | InvalidKeySpecException e) {
-            Debug.logError(e, MODULE);
-        }
-
-        // the 3DES KEK value
-        SecretKey loadedKek = this.getDesEdeKey(kekBytes);
-        byte[] loadKekBytes = loadedKek.getEncoded();
-
-        // test the KEK
-        byte[] rawIv = new byte[8];
-        SECURE_RANDOM.nextBytes(rawIv);
-        IvParameterSpec iv = new IvParameterSpec(rawIv);
-
-        Cipher cipher = null;
-        try {
-            cipher = getCipher(this.getKekKey(), Cipher.ENCRYPT_MODE, iv);
-        } catch (GeneralException e1) {
-            Debug.logError(e1, MODULE);
-        }
-        byte[] kekTestB = {0, 0, 0, 0, 0, 0, 0, 0 };
-        byte[] kekTestC = new byte[0];
-        if (kekTest != null) {
-            kekTestB = StringUtil.fromHexString(kekTest);
-        }
-
-        // encrypt the test bytes
-        try {
-            kekTestC = cipher.doFinal(kekTestB);
-        } catch (Exception e) {
-            Debug.logError(e, MODULE);
-        }
-
-        if (!kekOnly) {
-            // public key (just Y)
-            BigInteger y = publicKey.getY();
-            byte[] yBytes = y.toByteArray();
-            String yHex = StringUtil.toHexString(yBytes);
-            buf.append("======== Begin Public Key (Y @ ").append(yBytes.length).append(" / ").append(yHex.length()).append(") ========\n");
-            buf.append(yHex).append("\n");
-            buf.append("======== End Public Key ========\n\n");
-
-            // private key (just X)
-            BigInteger x = privateKey.getX();
-            byte[] xBytes = x.toByteArray();
-            String xHex = StringUtil.toHexString(xBytes);
-            buf.append("======== Begin Private Key (X @ ").append(xBytes.length).append(" / ").append(xHex.length()).append(") ========\n");
-            buf.append(xHex).append("\n");
-            buf.append("======== End Private Key ========\n\n");
-
-            // private key (full)
-            byte[] privateBytes = privateKey.getEncoded();
-            String privateHex = StringUtil.toHexString(privateBytes);
-            buf.append("======== Begin Private Key (Full @ ").append(privateBytes.length).append(" / ").append(privateHex.length())
-                    .append(") ========\n");
-            buf.append(privateHex).append("\n");
-            buf.append("======== End Private Key ========\n\n");
-        }
-
-        if (kekBytes != null) {
-            buf.append("======== Begin KEK (").append(kekBytes.length).append(") ========\n");
-            buf.append(StringUtil.toHexString(kekBytes)).append("\n");
-            buf.append("======== End KEK ========\n\n");
-
-            buf.append("======== Begin KEK (DES) (").append(loadKekBytes.length).append(") ========\n");
-            buf.append(StringUtil.toHexString(loadKekBytes)).append("\n");
-            buf.append("======== End KEK (DES) ========\n\n");
-
-            buf.append("======== Begin KEK Test (").append(kekTestC.length).append(") ========\n");
-            buf.append(StringUtil.toHexString(kekTestC)).append("\n");
-            buf.append("======== End KEK Test ========\n\n");
-        } else {
-            Debug.logError("KEK came back empty", MODULE);
-        }
-
-        return buf;
-    }
-
-    /**
-     * Create a set of public/private keys using ValueLinks defined parameters
-     * @return KeyPair object containing both public and private keys
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidAlgorithmParameterException
-     */
-    public KeyPair createKeys() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeySpecException {
-        // initialize the parameter spec
-        DHPublicKey publicKey = (DHPublicKey) this.getValueLinkPublicKey();
-        DHParameterSpec dhParamSpec = publicKey.getParams();
-        // create the public/private key pair using parameters defined by valuelink
-        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DH");
-        keyGen.initialize(dhParamSpec);
-        KeyPair keyPair = keyGen.generateKeyPair();
-
-        return keyPair;
-    }
-
-    /**
-     * Generate a key exchange key for use in encrypting the mwk
-     * @param privateKey The private key for the merchant
-     * @return byte array containing the kek
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeySpecException
-     * @throws InvalidKeyException
-     */
-    public byte[] generateKek(PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
-        // get the ValueLink public key
-        PublicKey vlPublic = this.getValueLinkPublicKey();
-
-        // generate shared secret key
-        KeyAgreement ka = KeyAgreement.getInstance("DH");
-        ka.init(privateKey);
-        ka.doPhase(vlPublic, true);
-        byte[] secretKey = ka.generateSecret();
-
-        if (debug) {
-            Debug.logInfo("Secret Key : " + StringUtil.toHexString(secretKey) + " / " + secretKey.length, MODULE);
-        }
-
-        // generate 3DES from secret key using VL algorithm (KEK)
-        MessageDigest md = MessageDigest.getInstance("SHA1");
-        byte[] digest = md.digest(secretKey);
-        byte[] des2 = getByteRange(digest, 0, 16);
-        byte[] first8 = getByteRange(des2, 0, 8);
-        byte[] kek = copyBytes(des2, first8, 0);
-
-        if (debug) {
-            Debug.logInfo("Generated KEK : " + StringUtil.toHexString(kek) + " / " + kek.length, MODULE);
-        }
-
-        return kek;
-    }
-
-    /**
-     * Get a public key object for the ValueLink supplied public key
-     * @return PublicKey object of ValueLinks's public key
-     * @throws NoSuchAlgorithmException
-     * @throws InvalidKeySpecException
-     */
-    public PublicKey getValueLinkPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
-        // read the valuelink public key
-        String publicValue = (String) props.get("payment.valuelink.publicValue");
-        byte[] publicKeyBytes = StringUtil.fromHexString(publicValue);
-
-        // initialize the parameter spec
-        DHParameterSpec dhParamSpec = this.getDHParameterSpec();
-
-        // load the valuelink public key
-        KeyFactory keyFactory = KeyFactory.getInstance("DH");
-        BigInteger publicKeyInt = new BigInteger(publicKeyBytes);
-        DHPublicKeySpec dhPublicSpec = new DHPublicKeySpec(publicKeyInt, dhParamSpec.getP(), dhParamSpec.getG());
-        PublicKey vlPublic = keyFactory.generatePublic(dhPublicSpec);
-
-        return vlPublic;
-    }
-
-    /**
-     * Get merchant Private Key
-     * @return PrivateKey object for the merchant
-     */
-    public PrivateKey getPrivateKey() throws InvalidKeySpecException, NoSuchAlgorithmException {
-        byte[] privateKeyBytes = this.getPrivateKeyBytes();
-
-        // initialize the parameter spec
-        DHParameterSpec dhParamSpec = this.getDHParameterSpec();
-
-        // load the private key
-        KeyFactory keyFactory = KeyFactory.getInstance("DH");
-        BigInteger privateKeyInt = new BigInteger(privateKeyBytes);
-        DHPrivateKeySpec dhPrivateSpec = new DHPrivateKeySpec(privateKeyInt, dhParamSpec.getP(), dhParamSpec.getG());
-        PrivateKey privateKey = keyFactory.generatePrivate(dhPrivateSpec);
-
-        return privateKey;
-    }
-
-    /**
-     * Generate a new MWK
-     * @return Hex String of the new encrypted MWK ready for transmission to ValueLink @
-     */
-    public byte[] generateMwk() {
-        KeyGenerator keyGen = null;
-        try {
-            keyGen = KeyGenerator.getInstance("DES");
-        } catch (NoSuchAlgorithmException e) {
-            Debug.logError(e, MODULE);
-        }
-
-        // generate the DES key 1
-        SecretKey des1 = keyGen.generateKey();
-        SecretKey des2 = keyGen.generateKey();
-
-        if (des1 != null && des2 != null) {
-            byte[] desByte1 = des1.getEncoded();
-            byte[] desByte2 = des2.getEncoded();
-            byte[] desByte3 = des1.getEncoded();
-
-            // check for weak keys
-            try {
-                if (DESKeySpec.isWeak(des1.getEncoded(), 0) || DESKeySpec.isWeak(des2.getEncoded(), 0)) {
-                    return generateMwk();
-                }
-            } catch (Exception e) {
-                Debug.logError(e, MODULE);
-            }
-
-            byte[] des3 = copyBytes(desByte1, copyBytes(desByte2, desByte3, 0), 0);
-            return generateMwk(des3);
-        }
-
-        Debug.logInfo("Null DES keys returned", MODULE);
-        return null;
-    }
-
-    /**
-     * Generate a new MWK
-     * @param desBytes byte array of the DES key (24 bytes)
-     * @return Hex String of the new encrypted MWK ready for transmission to ValueLink @
-     */
-    public byte[] generateMwk(byte[] desBytes) {
-        if (debug) {
-            Debug.logInfo("DES Key : " + StringUtil.toHexString(desBytes) + " / " + desBytes.length, MODULE);
-        }
-        SecretKeyFactory skf1 = null;
-        SecretKey mwk = null;
-        try {
-            skf1 = SecretKeyFactory.getInstance("DESede");
-        } catch (NoSuchAlgorithmException e) {
-            Debug.logError(e, MODULE);
-        }
-        DESedeKeySpec desedeSpec2 = null;
-        try {
-            desedeSpec2 = new DESedeKeySpec(desBytes);
-        } catch (InvalidKeyException e) {
-            Debug.logError(e, MODULE);
-        }
-        if (skf1 != null && desedeSpec2 != null) {
-            try {
-                mwk = skf1.generateSecret(desedeSpec2);
-            } catch (InvalidKeySpecException e) {
-                Debug.logError(e, MODULE);
-            }
-        }
-        if (mwk != null) {
-            return generateMwk(mwk);
-        }
-        return null;
-    }
-
-    /**
-     * Generate a new MWK
-     * @param mwkdes3 pre-generated DES3 SecretKey
-     * @return Hex String of the new encrypted MWK ready for transmission to ValueLink @
-     */
-    public byte[] generateMwk(SecretKey mwkdes3) {
-        // zeros for checksum
-        byte[] zeros = {0, 0, 0, 0, 0, 0, 0, 0 };
-
-        // 8 bytes random data
-        byte[] random = new byte[8];
-
-        byte[] rawIv = new byte[8];
-        SECURE_RANDOM.nextBytes(rawIv);
-        IvParameterSpec iv = new IvParameterSpec(rawIv);
-
-        // open a cipher using the new mwk
-        Cipher cipher = null;
-        try {
-            cipher = getCipher(mwkdes3, Cipher.ENCRYPT_MODE, iv);
-        } catch (GeneralException e1) {
-            Debug.logError(e1, MODULE);
-        }
-
-        // make the checksum - encrypted 8 bytes of 0's
-        byte[] encryptedZeros = new byte[0];
-        try {
-            encryptedZeros = cipher.doFinal(zeros);
-        } catch (IllegalStateException | BadPaddingException | IllegalBlockSizeException e) {
-            Debug.logError(e, MODULE);
-        }
-
-        // make the 40 byte MWK - random 8 bytes + key + checksum
-        byte[] newMwk = copyBytes(mwkdes3.getEncoded(), encryptedZeros, 0);
-        newMwk = copyBytes(random, newMwk, 0);
-
-        if (debug) {
-            Debug.logInfo("Random 8 byte : " + StringUtil.toHexString(random), MODULE);
-            Debug.logInfo("Encrypted 0's : " + StringUtil.toHexString(encryptedZeros), MODULE);
-            Debug.logInfo("Decrypted MWK : " + StringUtil.toHexString(mwkdes3.getEncoded()) + " / " + mwkdes3.getEncoded().length, MODULE);
-            Debug.logInfo("Encrypted MWK : " + StringUtil.toHexString(newMwk) + " / " + newMwk.length, MODULE);
-        }
-
-        return newMwk;
-    }
-
-    /**
-     * Use the KEK to encrypt a value usually the MWK
-     * @param content byte array to encrypt
-     * @return encrypted byte array
-     */
-    public byte[] encryptViaKek(byte[] content) {
-        try {
-            return cryptoViaKek(content, Cipher.ENCRYPT_MODE);
-        } catch (GeneralException e1) {
-            Debug.logError(e1, MODULE);
-        }
-        return null;
-    }
-
-    /**
-     * Ue the KEK to decrypt a value
-     * @param content byte array to decrypt
-     * @return decrypted byte array
-     */
-    public byte[] decryptViaKek(byte[] content) {
-        try {
-            return cryptoViaKek(content, Cipher.DECRYPT_MODE);
-        } catch (GeneralException e1) {
-            Debug.logError(e1, MODULE);
-        }
-        return null;
-    }
-
-    /**
-     * Returns a date string formatted as directed by ValueLink
-     * @return ValueLink formatted date String
-     */
-    public String getDateString() {
-        String format = (String) props.get("payment.valuelink.timestamp");
-        SimpleDateFormat sdf = new SimpleDateFormat(format);
-        return sdf.format(new Date());
-    }
-
-    /**
-     * Returns the current working key index
-     * @return Long number of the current working key index
-     */
-    public Long getWorkingKeyIndex() {
-        if (this.mwkIndex == null) {
-            synchronized (this) {
-                if (this.mwkIndex == null) {
-                    this.mwkIndex = this.getGenericValue().getLong("workingKeyIndex");
-                }
-            }
-        }
-
-        if (debug) {
-            Debug.logInfo("Current Working Key Index : " + this.mwkIndex, MODULE);
-        }
-
-        return this.mwkIndex;
-    }
-
-    /**
-     * Returns a ValueLink formatted amount String
-     * @param amount BigDecimal value to format
-     * @return Formatted String
-     */
-    public String getAmount(BigDecimal amount) {
-        if (amount == null) {
-            return "0.00";
-        }
-        return Integer.toString(amount.movePointRight(2).intValue());
-    }
-
-    /**
-     * Returns a BigDecimal from a ValueLink formatted amount String
-     * @param amount The ValueLink formatted amount String
-     * @return BigDecimal object
-     */
-    public BigDecimal getAmount(String amount) {
-        if (amount == null) {
-            return BigDecimal.ZERO;
-        }
-        BigDecimal amountBd = new BigDecimal(amount);
-        return amountBd.movePointLeft(2);
-    }
-
-    /**
-     * Gets currency.
-     * @param currency the currency
-     * @return the currency
-     */
-    public String getCurrency(String currency) {
-        return "840"; // todo make this multi-currency
-    }
-
-    /**
-     * Creates a Map of initial request values (MerchID, AltMerchNo, Modes, MerchTime, TermTxnNo, EncryptID)
-     * Note: For 2010 (assign working key) transaction, the EncryptID will need to be adjusted
-     * @return Map containing the inital request values
-     */
-    public Map<String, Object> getInitialRequestMap(Map<String, Object> context) {
-        Map<String, Object> request = new HashMap<>();
-
-        // merchant information
-        request.put("MerchID", merchantId + terminalId);
-        request.put("AltMerchNo", props.get("payment.valuelink.altMerchantId"));
-
-        // mode settings
-        String modes = (String) props.get("payment.valuelink.modes");
-        if (UtilValidate.isNotEmpty(modes)) {
-            request.put("Modes", modes);
-        }
-
-        // merchant timestamp
-        String merchTime = (String) context.get("MerchTime");
-        if (merchTime == null) {
-            merchTime = this.getDateString();
-        }
-        request.put("MerchTime", merchTime);
-
-        // transaction number
-        String termTxNo = (String) context.get("TermTxnNo");
-        if (termTxNo == null) {
-            termTxNo = delegator.getNextSeqId("ValueLinkKey");
-        }
-        request.put("TermTxnNo", termTxNo);
-
-        // current working key index
-        request.put("EncryptID", this.getWorkingKeyIndex());
-
-        if (debug) {
-            Debug.logInfo("Created Initial Request Map : " + request, MODULE);
-        }
-
-        return request;
-    }
-
-    /**
-     * Gets the cached value object for this merchant's keys
-     * @return Cached GenericValue object
-     */
-    public GenericValue getGenericValue() {
-        GenericValue value = null;
-        try {
-            value = EntityQuery.use(delegator).from("ValueLinkKey").where("merchantId", merchantId).cache().queryOne();
-        } catch (GenericEntityException e) {
-            Debug.logError(e, MODULE);
-        }
-        if (value == null) {
-            throw new RuntimeException("No ValueLinkKey record found for Merchant ID : " + merchantId);
-        }
-        return value;
-    }
-
-    /**
-     * Reloads the keys in the object cache; use this when re-creating keys
-     */
-    public void reload() {
-        this.kek = null;
-        this.mwk = null;
-        this.mwkIndex = null;
-    }
-
-    /** using the prime and generator provided by valuelink; create a parameter object */
-    protected DHParameterSpec getDHParameterSpec() {
-        String primeHex = (String) props.get("payment.valuelink.prime");
-        String genString = (String) props.get("payment.valuelink.generator");
-
-        // convert the p/g hex values
-        byte[] primeByte = StringUtil.fromHexString(primeHex);
-        BigInteger prime = new BigInteger(1, primeByte); // force positive (unsigned)
-        BigInteger generator = new BigInteger(genString);
-
-        // initialize the parameter spec
-        DHParameterSpec dhParamSpec = new DHParameterSpec(prime, generator, 1024);
-
-        return dhParamSpec;
-    }
-
-    /**
-     * actual kek encryption/decryption code
-     * @throws GeneralException
-     */
-    protected byte[] cryptoViaKek(byte[] content, int mode) throws GeneralException {
-        // open a cipher using the kek for transport
-        byte[] rawIv = new byte[8];
-        SECURE_RANDOM.nextBytes(rawIv);
-        IvParameterSpec iv = new IvParameterSpec(rawIv);
-
-        // Create the Cipher - DESede/CBC/PKCS5Padding
-        byte[] encBytes = null;
-        Cipher cipher = getCipher(getKekKey(), mode, iv);
-        try {
-            encBytes = cipher.doFinal(content);
-        } catch (IllegalStateException | BadPaddingException | IllegalBlockSizeException e) {
-            Debug.logError(e, MODULE);
-        }
-        // Prepend iv as a prefix to use it during decryption
-        byte[] combinedPayload = new byte[rawIv.length + encBytes.length];
-
-        // populate payload with prefix iv and encrypted data
-        System.arraycopy(iv, 0, combinedPayload, 0, rawIv.length);
-        System.arraycopy(cipher, 0, combinedPayload, 8, encBytes.length);
-
-        return encBytes;
-    }
-
-    // return a cipher for a key - DESede/CBC/NoPadding with random IV
-    protected static Cipher getCipher(Key key, int mode, IvParameterSpec iv) throws GeneralException {
-        // create the Cipher - DESede/CBC/NoPadding
-        Cipher cipher = null;
-        try {
-            cipher = Cipher.getInstance("DESede/CBC/NoPadding");
-        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
-            throw new GeneralException(e);
-        }
-        try {
-            cipher.init(mode, key, iv);
-        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
-            throw new GeneralException(e);
-        }
-        return cipher;
-    }
-
-    /**
-     * Get pin check sum byte [ ].
-     * @param pinBytes the pin bytes
-     * @return the byte [ ]
-     */
-    protected byte[] getPinCheckSum(byte[] pinBytes) {
-        byte[] checkSum = new byte[1];
-        checkSum[0] = 0;
-        for (byte pinByte : pinBytes) {
-            checkSum[0] += pinByte;
-        }
-        return checkSum;
-    }
-
-    /**
-     * Get random bytes byte [ ].
-     * @param length the length
-     * @return the byte [ ]
-     */
-    protected byte[] getRandomBytes(int length) {
-        byte[] randomBytes = new byte[length];
-        SECURE_RANDOM.nextBytes(randomBytes);
-        return randomBytes;
-    }
-
-    /**
-     * Gets mwk key.
-     * @return the mwk key
-     */
-    protected SecretKey getMwkKey() {
-        if (mwk == null) {
-            mwk = this.getDesEdeKey(getByteRange(getMwk(), 8, 24));
-        }
-
-        if (debug) {
-            Debug.logInfo("Raw MWK : " + StringUtil.toHexString(getMwk()), MODULE);
-            Debug.logInfo("MWK : " + StringUtil.toHexString(mwk.getEncoded()), MODULE);
-        }
-
-        return mwk;
-    }
-
-    /**
-     * Gets kek key.
-     * @return the kek key
-     */
-    protected SecretKey getKekKey() {
-        if (kek == null) {
-            kek = this.getDesEdeKey(getKek());
-        }
-
-        if (debug) {
-            Debug.logInfo("Raw KEK : " + StringUtil.toHexString(getKek()), MODULE);
-            Debug.logInfo("KEK : " + StringUtil.toHexString(kek.getEncoded()), MODULE);
-        }
-
-        return kek;
-    }
-
-    /**
-     * Gets des ede key.
-     * @param rawKey the raw key
-     * @return the des ede key
-     */
-    protected SecretKey getDesEdeKey(byte[] rawKey) {
-        SecretKeyFactory skf = null;
-        try {
-            skf = SecretKeyFactory.getInstance("DESede");
-        } catch (NoSuchAlgorithmException e) {
-            // should never happen since DESede is a standard algorithm
-            Debug.logError(e, MODULE);
-            return null;
-        }
-
-        // load the raw key
-        if (rawKey.length > 0) {
-            DESedeKeySpec desedeSpec1 = null;
-            try {
-                desedeSpec1 = new DESedeKeySpec(rawKey);
-            } catch (InvalidKeyException e) {
-                Debug.logError(e, "Not a valid DESede key", MODULE);
-                return null;
-            }
-
-            // create the SecretKey Object
-            SecretKey key = null;
-            try {
-                key = skf.generateSecret(desedeSpec1);
-            } catch (InvalidKeySpecException e) {
-                Debug.logError(e, MODULE);
-            }
-            return key;
-        } else {
-            throw new RuntimeException("No valid DESede key available");
-        }
-    }
-
-    /**
-     * Get mwk byte [ ].
-     * @return the byte [ ]
-     */
-    protected byte[] getMwk() {
-        return StringUtil.fromHexString(this.getGenericValue().getString("workingKey"));
-    }
-
-    /**
-     * Get kek byte [ ].
-     * @return the byte [ ]
-     */
-    protected byte[] getKek() {
-        return StringUtil.fromHexString(this.getGenericValue().getString("exchangeKey"));
-    }
-
-    /**
-     * Get private key bytes byte [ ].
-     * @return the byte [ ]
-     */
-    protected byte[] getPrivateKeyBytes() {
-        return StringUtil.fromHexString(this.getGenericValue().getString("privateKey"));
-    }
-
-    /**
-     * Parse response map.
-     * @param response the response
-     * @return the map
-     */
-    protected Map<String, Object> parseResponse(String response) {
-        if (debug) {
-            Debug.logInfo("Raw Response : " + response, MODULE);
-        }
-
-        // covert to all lowercase and trim off the html header
-        String subResponse = response.toLowerCase(Locale.getDefault());
-        int firstIndex = subResponse.indexOf("<tr>");
-        int lastIndex = subResponse.lastIndexOf("</tr>");
-        subResponse = subResponse.substring(firstIndex, lastIndex);
-
-        // check for a history table
-        String history = null;
-        List<Map<String, String>> historyMapList = null;
-        if (subResponse.indexOf("<table") > -1) {
-            int startHistory = subResponse.indexOf("<table");
-            int endHistory = subResponse.indexOf("</table>") + 8;
-            history = subResponse.substring(startHistory, endHistory);
-
-            // replace the subResponse string so it doesn't conflict
-            subResponse = StringUtil.replaceString(subResponse, history, "[_HISTORY_]");
-
-            // parse the history into a list of maps
-            historyMapList = this.parseHistoryResponse(history);
-        }
-
-        // replace all end rows with | this is the name delimiter
-        subResponse = StringUtil.replaceString(subResponse, "</tr>", "|");
-
-        // replace all </TD><TD> with = this is the value delimiter
-        subResponse = StringUtil.replaceString(subResponse, "</td><td>", "=");
-
-        // clean off a bunch of other useless stuff
-        subResponse = StringUtil.replaceString(subResponse, "<tr>", "");
-        subResponse = StringUtil.replaceString(subResponse, "<td>", "");
-        subResponse = StringUtil.replaceString(subResponse, "</td>", "");
-
-        // make the map
-        Map<String, Object> responseMap = new HashMap<>();
-        responseMap.putAll(StringUtil.strToMap(subResponse, true));
-
-        // add the raw html back in just in case we need it later
-        responseMap.put("_rawHtmlResponse", response);
-
-        // if we have a history add it back in
-        if (history != null) {
-            responseMap.put("_rawHistoryHtml", history);
-            responseMap.put("history", historyMapList);
-        }
-
-        if (debug) {
-            Debug.logInfo("Response Map : " + responseMap, MODULE);
-        }
-
-        return responseMap;
-    }
-
-    private List<Map<String, String>> parseHistoryResponse(String response) {
-        if (debug) {
-            Debug.logInfo("Raw History : " + response, MODULE);
-        }
-
-        // covert to all lowercase and trim off the html header
-        String subResponse = response.toLowerCase(Locale.getDefault());
-        int firstIndex = subResponse.indexOf("<tr>");
-        int lastIndex = subResponse.lastIndexOf("</tr>");
-        subResponse = subResponse.substring(firstIndex, lastIndex);
-
-        // clean up the html and replace the delimiters with '|'
-        subResponse = StringUtil.replaceString(subResponse, "<td>", "");
-        subResponse = StringUtil.replaceString(subResponse, "</td>", "|");
-
-        // test the string to make sure we have fields to parse
-        String testResponse = StringUtil.replaceString(subResponse, "<tr>", "");
-        testResponse = StringUtil.replaceString(testResponse, "</tr>", "");
-        testResponse = StringUtil.replaceString(testResponse, "|", "");
-        testResponse = testResponse.trim();
-        if (testResponse.isEmpty()) {
-            if (debug) {
-                Debug.logInfo("History did not contain any fields, returning null", MODULE);
-            }
-            return null;
-        }
-
-        // break up the keys from the values
-        int valueStart = subResponse.indexOf("</tr>");
-        String keys = subResponse.substring(4, valueStart - 1);
-        String values = subResponse.substring(valueStart + 9, subResponse.length() - 6);
-
-        // split sets of values up
-        values = StringUtil.replaceString(values, "|</tr><tr>", "&");
-        List<String> valueList = StringUtil.split(values, "&");
-
-        // create a List of Maps for each set of values
-        List<Map<String, String>> valueMap = new LinkedList<>();
-        for (String s : valueList) {
-            valueMap.add(StringUtil.createMap(StringUtil.split(keys, "|"), StringUtil.split(s, "|")));
-        }
-
-        if (debug) {
-            Debug.logInfo("History Map : " + valueMap, MODULE);
-        }
-
-        return valueMap;
-    }
-
-    /**
-     * Returns a new byte[] from the offset of the defined byte[] with a specific number of bytes
-     * @param bytes The byte[] to extract from
-     * @param offset The starting postition
-     * @param length The number of bytes to copy
-     * @return a new byte[]
-     */
-    public static byte[] getByteRange(byte[] bytes, int offset, int length) {
-        byte[] newBytes = new byte[length];
-        for (int i = 0; i < length; i++) {
-            newBytes[i] = bytes[offset + i];
-        }
-        return newBytes;
-    }
-
-    /**
-     * Copies a byte[] into another byte[] starting at a specific position
-     * @param source byte[] to copy from
-     * @param target byte[] coping into
-     * @param position the position on target where source will be copied to
-     * @return a new byte[]
-     */
-    public static byte[] copyBytes(byte[] source, byte[] target, int position) {
-        byte[] newBytes = new byte[target.length + source.length];
-        for (int i = 0, n = 0, x = 0; i < newBytes.length; i++) {
-            if (i < position || i > (position + source.length - 2)) {
-                newBytes[i] = target[n];
-                n++;
-            } else {
-                for (; x < source.length; x++) {
-                    newBytes[i] = source[x];
-                    if (source.length - 1 > x) {
-                        i++;
-                    }
-                }
-            }
-        }
-        return newBytes;
-    }
-}
diff --git a/applications/accounting/src/main/java/org/apache/ofbiz/accounting/thirdparty/valuelink/ValueLinkServices.java b/applications/accounting/src/main/java/org/apache/ofbiz/accounting/thirdparty/valuelink/ValueLinkServices.java
deleted file mode 100644
index 7d33fc5702..0000000000
--- a/applications/accounting/src/main/java/org/apache/ofbiz/accounting/thirdparty/valuelink/ValueLinkServices.java
+++ /dev/null
@@ -1,1470 +0,0 @@
-/*******************************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *******************************************************************************/
-package org.apache.ofbiz.accounting.thirdparty.valuelink;
-
-import java.math.BigDecimal;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-
-import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.HttpClientException;
-import org.apache.ofbiz.base.util.StringUtil;
-import org.apache.ofbiz.base.util.UtilDateTime;
-import org.apache.ofbiz.base.util.UtilMisc;
-import org.apache.ofbiz.base.util.UtilProperties;
-import org.apache.ofbiz.base.util.UtilValidate;
-import org.apache.ofbiz.entity.Delegator;
-import org.apache.ofbiz.entity.GenericEntityException;
-import org.apache.ofbiz.entity.GenericValue;
-import org.apache.ofbiz.entity.util.EntityQuery;
-import org.apache.ofbiz.entity.util.EntityUtilProperties;
-import org.apache.ofbiz.order.order.OrderReadHelper;
-import org.apache.ofbiz.product.store.ProductStoreWorker;
-import org.apache.ofbiz.service.DispatchContext;
-import org.apache.ofbiz.service.GenericServiceException;
-import org.apache.ofbiz.service.LocalDispatcher;
-import org.apache.ofbiz.service.ModelService;
-import org.apache.ofbiz.service.ServiceUtil;
-
-/**
- * ValueLinkServices - Integration with ValueLink Gift Cards
- */
-public class ValueLinkServices {
-
-    private static final String MODULE = ValueLinkServices.class.getName();
-    private static final String RESOURCE = "AccountingUiLabels";
-    private static final String RES_ERROR = "AccountingErrorUiLabels";
-    private static final String RES_ORDER = "OrderUiLabels";
-
-    // generate/display new public/private/kek keys
-    public static Map<String, Object> createKeys(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        Properties props = getProperties(context);
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        vl.reload();
-
-        Boolean kekOnly = context.get("kekOnly") != null ? (Boolean) context.get("kekOnly") : Boolean.FALSE;
-        String kekTest = (String) context.get("kekTest");
-        Debug.logInfo("KEK Only : " + kekOnly, MODULE);
-
-        StringBuffer buf = vl.outputKeyCreation(kekOnly, kekTest);
-        String output = buf.toString();
-        Debug.logInfo(":: Key Generation Output ::\n\n" + output, MODULE);
-
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-        result.put("output", output);
-        return result;
-    }
-
-    // test the KEK encryption
-    public static Map<String, Object> testKekEncryption(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        //GenericValue userLogin = (GenericValue) context.get("userLogin");
-        Properties props = getProperties(context);
-
-        // get an api instance
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        vl.reload();
-
-        String testString = (String) context.get("kekTest");
-        Integer mode = (Integer) context.get("mode");
-        byte[] testBytes = StringUtil.fromHexString(testString);
-
-        // place holder
-        byte[] testEncryption = null;
-        String desc = "";
-
-        if (mode == 1) {
-            // encrypt the test bytes
-            testEncryption = vl.encryptViaKek(testBytes);
-            desc = "Encrypted";
-        } else {
-            // decrypt the test bytes
-            testEncryption = vl.decryptViaKek(testBytes);
-            desc = "Decrypted";
-        }
-
-        // setup the output
-        StringBuilder buf = new StringBuilder();
-        buf.append("======== Begin Test String (").append(testString.length()).append(") ========\n");
-        buf.append(testString).append("\n");
-        buf.append("======== End Test String ========\n\n");
-
-        buf.append("======== Begin Test Bytes (").append(testBytes.length).append(") ========\n");
-        buf.append(StringUtil.toHexString(testBytes)).append("\n");
-        buf.append("======== End Test Bytes ========\n\n");
-
-        buf.append("======== Begin Test Bytes ").append(desc).append(" (").append(testEncryption.length).append(") ========\n");
-        buf.append(StringUtil.toHexString(testEncryption)).append("\n");
-        buf.append("======== End Test Bytes ").append(desc).append(" ========\n\n");
-
-        String output = buf.toString();
-        Debug.logInfo(":: KEK Test Output ::\n\n" + output, MODULE);
-
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-        result.put("output", output);
-        return result;
-    }
-
-    // change working key service
-    public static Map<String, Object> assignWorkingKey(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        GenericValue userLogin = (GenericValue) context.get("userLogin");
-        Properties props = getProperties(context);
-        Locale locale = (Locale) context.get("locale");
-
-        // get an api instance
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        vl.reload();
-
-        // place holder
-        byte[] mwk = null;
-
-        // see if we passed in the DES hex string
-        String desHexString = (String) context.get("desHexString");
-        if (UtilValidate.isEmpty(desHexString)) {
-            mwk = vl.generateMwk();
-        } else {
-            mwk = vl.generateMwk(StringUtil.fromHexString(desHexString));
-        }
-
-        // encrypt the mwk
-        String mwkHex = StringUtil.toHexString(vl.encryptViaKek(mwk));
-
-        // build the request
-        Map<String, Object> request = vl.getInitialRequestMap(context);
-        request.put("Interface", "Encrypt");
-        request.put("EncryptKey", mwkHex);
-        request.put("EncryptID", vl.getWorkingKeyIndex() + 1);
-
-        // send the request
-        Map<String, Object> response = null;
-        try {
-            response = vl.send(request);
-        } catch (HttpClientException e) {
-            Debug.logError(e, "Problem communicating with VL");
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkCannotUpdateWorkingKey", locale));
-        }
-        Debug.logInfo("Response : " + response, MODULE);
-
-        // on success update the database / reload the cached api
-        String responseCode = (String) response.get("responsecode");
-        if (!"00".equals(responseCode)) {
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkTransactionFailed",
-                    UtilMisc.toMap("responseCode", responseCode), locale));
-        }
-        GenericValue vlKeys = GenericValue.create(vl.getGenericValue());
-        vlKeys.set("lastWorkingKey", vlKeys.get("workingKey"));
-        vlKeys.set("workingKey", StringUtil.toHexString(mwk));
-        vlKeys.set("workingKeyIndex", request.get("EncryptID"));
-        vlKeys.set("lastModifiedDate", UtilDateTime.nowTimestamp());
-        vlKeys.set("lastModifiedByUserLogin", userLogin != null ? userLogin.get("userLoginId") : null);
-        try {
-            vlKeys.store();
-        } catch (GenericEntityException e) {
-            Debug.logError(e, "Unable to store updated keys; the keys were changed with ValueLink : " + vlKeys, MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkCannotStoreWorkingKey", locale));
-        }
-        vl.reload();
-        return ServiceUtil.returnSuccess();
-    }
-
-    public static Map<String, Object> activate(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        Properties props = getProperties(context);
-        String vlPromoCode = (String) context.get("vlPromoCode");
-        String cardNumber = (String) context.get("cardNumber");
-        String pin = (String) context.get("pin");
-        String currency = (String) context.get("currency");
-        String orderId = (String) context.get("orderId");
-        String partyId = (String) context.get("partyId");
-        BigDecimal amount = (BigDecimal) context.get("amount");
-        Locale locale = (Locale) context.get("locale");
-
-        // override interface for void/rollback
-        String iFace = (String) context.get("Interface");
-
-        // get an api instance
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        Map<String, Object> request = vl.getInitialRequestMap(context);
-        request.put("Interface", iFace != null ? iFace : "Activate");
-        if (UtilValidate.isNotEmpty(vlPromoCode)) {
-            request.put("PromoCode", vlPromoCode);
-        }
-        request.put("Amount", vl.getAmount(amount));
-        request.put("LocalCurr", vl.getCurrency(currency));
-
-        if (UtilValidate.isNotEmpty(cardNumber)) {
-            request.put("CardNo", cardNumber);
-        }
-        if (UtilValidate.isNotEmpty(pin)) {
-            request.put("PIN", vl.encryptPin(pin));
-        }
-
-        // user defined field #1
-        if (UtilValidate.isNotEmpty(orderId)) {
-            request.put("User1", orderId);
-        }
-
-        // user defined field #2
-        if (UtilValidate.isNotEmpty(partyId)) {
-            request.put("User2", partyId);
-        }
-
-        // set the timeout reversal
-        setTimeoutReversal(dctx, context, request);
-
-        // send the request
-        Map<String, Object> response = null;
-        try {
-            response = vl.send(request);
-        } catch (HttpClientException e) {
-            Debug.logError(e, "Problem communicating with VL");
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToActivateGiftCard", locale));
-        }
-
-        String responseCode = (String) response.get("responsecode");
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-        if ("00".equals(responseCode)) {
-            result.put("processResult", Boolean.TRUE);
-            result.put("pin", vl.decryptPin((String) response.get("pin")));
-        } else {
-            result.put("processResult", Boolean.FALSE);
-            result.put("pin", response.get("PIN"));
-        }
-        result.put("responseCode", responseCode);
-        result.put("authCode", response.get("authcode"));
-        result.put("cardNumber", response.get("cardno"));
-        result.put("amount", vl.getAmount((String) response.get("currbal")));
-        result.put("expireDate", response.get("expiredate"));
-        result.put("cardClass", response.get("cardclass"));
-        result.put("referenceNum", response.get("traceno"));
-        Debug.logInfo("Activate Result : " + result, MODULE);
-        return result;
-
-    }
-
-    public static Map<String, Object> linkPhysicalCard(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        Properties props = getProperties(context);
-        String virtualCard = (String) context.get("virtualCard");
-        String virtualPin = (String) context.get("virtualPin");
-        String physicalCard = (String) context.get("physicalCard");
-        String physicalPin = (String) context.get("physicalPin");
-        String partyId = (String) context.get("partyId");
-        Locale locale = (Locale) context.get("locale");
-
-        // get an api instance
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        Map<String, Object> request = vl.getInitialRequestMap(context);
-        request.put("Interface", "Link");
-        request.put("VCardNo", virtualCard);
-        request.put("VPIN", vl.encryptPin(virtualPin));
-        request.put("PCardNo", physicalCard);
-        request.put("PPIN", vl.encryptPin(physicalPin));
-
-        // user defined field #2
-        if (UtilValidate.isNotEmpty(partyId)) {
-            request.put("User2", partyId);
-        }
-
-        // send the request
-        Map<String, Object> response = null;
-        try {
-            response = vl.send(request);
-        } catch (HttpClientException e) {
-            Debug.logError(e, "Problem communicating with VL");
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToLinkGiftCard", locale));
-        }
-
-        String responseCode = (String) response.get("responsecode");
-        Map<String, Object> result = ServiceUtil.returnSuccess(UtilProperties.getMessage(RESOURCE,
-                "AccountingValueLinkGiftCardActivated", locale));
-
-        result.put("processResult", "00".equals(responseCode));
-        result.put("responseCode", responseCode);
-        result.put("authCode", response.get("authcode"));
-        result.put("amount", vl.getAmount((String) response.get("newbal")));
-        result.put("expireDate", response.get("expiredate"));
-        result.put("cardClass", response.get("cardclass"));
-        result.put("referenceNum", response.get("traceno"));
-        Debug.logInfo("Link Result : " + result, MODULE);
-        return result;
-    }
-
-    public static Map<String, Object> disablePin(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        Properties props = getProperties(context);
-        String cardNumber = (String) context.get("cardNumber");
-        String pin = (String) context.get("pin");
-        String orderId = (String) context.get("orderId");
-        String partyId = (String) context.get("partyId");
-        BigDecimal amount = (BigDecimal) context.get("amount");
-        Locale locale = (Locale) context.get("locale");
-
-        // get an api instance
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        Map<String, Object> request = vl.getInitialRequestMap(context);
-        request.put("Interface", "Disable");
-        request.put("CardNo", cardNumber);
-        request.put("PIN", vl.encryptPin(pin));
-        request.put("Amount", vl.getAmount(amount));
-
-        // user defined field #1
-        if (UtilValidate.isNotEmpty(orderId)) {
-            request.put("User1", orderId);
-        }
-
-        // user defined field #2
-        if (UtilValidate.isNotEmpty(partyId)) {
-            request.put("User2", partyId);
-        }
-
-        // send the request
-        Map<String, Object> response = null;
-        try {
-            response = vl.send(request);
-        } catch (HttpClientException e) {
-            Debug.logError(e, "Problem communicating with VL");
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToDisablePin", locale));
-        }
-
-        String responseCode = (String) response.get("responsecode");
-        Map<String, Object> result = ServiceUtil.returnSuccess(UtilProperties.getMessage(RESOURCE,
-                "AccountingValueLinkPinDisabled", locale));
-
-        result.put("processResult", "00".equals(responseCode));
-        result.put("responseCode", responseCode);
-        result.put("balance", vl.getAmount((String) response.get("currbal")));
-        result.put("expireDate", response.get("expiredate"));
-        result.put("cardClass", response.get("cardclass"));
-        result.put("referenceNum", response.get("traceno"));
-        Debug.logInfo("Disable Result : " + result, MODULE);
-        return result;
-    }
-
-    public static Map<String, Object> redeem(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        Properties props = getProperties(context);
-        String cardNumber = (String) context.get("cardNumber");
-        String pin = (String) context.get("pin");
-        String currency = (String) context.get("currency");
-        String orderId = (String) context.get("orderId");
-        String partyId = (String) context.get("partyId");
-        BigDecimal amount = (BigDecimal) context.get("amount");
-        Locale locale = (Locale) context.get("locale");
-
-        // override interface for void/rollback
-        String iFace = (String) context.get("Interface");
-
-        // get an api instance
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        Map<String, Object> request = vl.getInitialRequestMap(context);
-        request.put("Interface", iFace != null ? iFace : "Redeem");
-        request.put("CardNo", cardNumber);
-        request.put("PIN", vl.encryptPin(pin));
-        request.put("Amount", vl.getAmount(amount));
-        request.put("LocalCurr", vl.getCurrency(currency));
-
-        // user defined field #1
-        if (UtilValidate.isNotEmpty(orderId)) {
-            request.put("User1", orderId);
-        }
-
-        // user defined field #2
-        if (UtilValidate.isNotEmpty(partyId)) {
-            request.put("User2", partyId);
-        }
-
-        // set the timeout reversal
-        setTimeoutReversal(dctx, context, request);
-
-        // send the request
-        Map<String, Object> response = null;
-        try {
-            response = vl.send(request);
-        } catch (HttpClientException e) {
-            Debug.logError(e, "Problem communicating with VL");
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToRedeemGiftCard", locale));
-        }
-
-        String responseCode = (String) response.get("responsecode");
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-
-        result.put("processResult", "00".equals(responseCode));
-        result.put("responseCode", responseCode);
-        result.put("authCode", response.get("authcode"));
-        result.put("previousAmount", vl.getAmount((String) response.get("prevbal")));
-        result.put("amount", vl.getAmount((String) response.get("newbal")));
-        result.put("expireDate", response.get("expiredate"));
-        result.put("cardClass", response.get("cardclass"));
-        result.put("cashBack", vl.getAmount((String) response.get("cashback")));
-        result.put("referenceNum", response.get("traceno"));
-        Debug.logInfo("Redeem Result : " + result, MODULE);
-        return result;
-
-    }
-
-    public static Map<String, Object> reload(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        Properties props = getProperties(context);
-        String cardNumber = (String) context.get("cardNumber");
-        String pin = (String) context.get("pin");
-        String currency = (String) context.get("currency");
-        String orderId = (String) context.get("orderId");
-        String partyId = (String) context.get("partyId");
-        BigDecimal amount = (BigDecimal) context.get("amount");
-        Locale locale = (Locale) context.get("locale");
-
-        // override interface for void/rollback
-        String iFace = (String) context.get("Interface");
-
-        // get an api instance
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        Map<String, Object> request = vl.getInitialRequestMap(context);
-        request.put("Interface", iFace != null ? iFace : "Reload");
-        request.put("CardNo", cardNumber);
-        request.put("PIN", vl.encryptPin(pin));
-        request.put("Amount", vl.getAmount(amount));
-        request.put("LocalCurr", vl.getCurrency(currency));
-
-        // user defined field #1
-        if (UtilValidate.isNotEmpty(orderId)) {
-            request.put("User1", orderId);
-        }
-
-        // user defined field #2
-        if (UtilValidate.isNotEmpty(partyId)) {
-            request.put("User2", partyId);
-        }
-
-        // set the timeout reversal
-        setTimeoutReversal(dctx, context, request);
-
-        // send the request
-        Map<String, Object> response = null;
-        try {
-            response = vl.send(request);
-        } catch (HttpClientException e) {
-            Debug.logError(e, "Problem communicating with VL");
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToReloadGiftCard", locale));
-        }
-
-        String responseCode = (String) response.get("responsecode");
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-
-        result.put("processResult", "00".equals(responseCode));
-        result.put("responseCode", responseCode);
-        result.put("authCode", response.get("authcode"));
-        result.put("previousAmount", vl.getAmount((String) response.get("prevbal")));
-        result.put("amount", vl.getAmount((String) response.get("newbal")));
-        result.put("expireDate", response.get("expiredate"));
-        result.put("cardClass", response.get("cardclass"));
-        result.put("referenceNum", response.get("traceno"));
-        Debug.logInfo("Reload Result : " + result, MODULE);
-        return result;
-
-    }
-
-    public static Map<String, Object> balanceInquire(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        Properties props = getProperties(context);
-        String cardNumber = (String) context.get("cardNumber");
-        String pin = (String) context.get("pin");
-        String currency = (String) context.get("currency");
-        String orderId = (String) context.get("orderId");
-        String partyId = (String) context.get("partyId");
-        Locale locale = (Locale) context.get("locale");
-
-        // get an api instance
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        Map<String, Object> request = vl.getInitialRequestMap(context);
-        request.put("Interface", "Balance");
-        request.put("CardNo", cardNumber);
-        request.put("PIN", vl.encryptPin(pin));
-        request.put("LocalCurr", vl.getCurrency(currency));
-
-        // user defined field #1
-        if (UtilValidate.isNotEmpty(orderId)) {
-            request.put("User1", orderId);
-        }
-
-        // user defined field #2
-        if (UtilValidate.isNotEmpty(partyId)) {
-            request.put("User2", partyId);
-        }
-
-        // send the request
-        Map<String, Object> response = null;
-        try {
-            response = vl.send(request);
-        } catch (HttpClientException e) {
-            Debug.logError(e, "Problem communicating with VL");
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToCallBalanceInquiry", locale));
-        }
-
-        String responseCode = (String) response.get("responsecode");
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-
-        result.put("processResult", "00".equals(responseCode));
-        result.put("responseCode", responseCode);
-        result.put("balance", vl.getAmount((String) response.get("currbal")));
-        result.put("expireDate", response.get("expiredate"));
-        result.put("cardClass", response.get("cardclass"));
-        result.put("referenceNum", response.get("traceno"));
-        Debug.logInfo("Balance Result : " + result, MODULE);
-        return result;
-
-    }
-
-    public static Map<String, Object> transactionHistory(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        Properties props = getProperties(context);
-        String cardNumber = (String) context.get("cardNumber");
-        String pin = (String) context.get("pin");
-        String orderId = (String) context.get("orderId");
-        String partyId = (String) context.get("partyId");
-        Locale locale = (Locale) context.get("locale");
-
-        // get an api instance
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        Map<String, Object> request = vl.getInitialRequestMap(context);
-        request.put("Interface", "History");
-        request.put("CardNo", cardNumber);
-        request.put("PIN", vl.encryptPin(pin));
-
-        // user defined field #1
-        if (UtilValidate.isNotEmpty(orderId)) {
-            request.put("User1", orderId);
-        }
-
-        // user defined field #2
-        if (UtilValidate.isNotEmpty(partyId)) {
-            request.put("User2", partyId);
-        }
-
-        // send the request
-        Map<String, Object> response = null;
-        try {
-            response = vl.send(request);
-        } catch (HttpClientException e) {
-            Debug.logError(e, "Problem communicating with VL");
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToCallHistoryInquiry", locale));
-        }
-
-        String responseCode = (String) response.get("responsecode");
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-
-        result.put("processResult", "00".equals(responseCode));
-        result.put("responseCode", responseCode);
-        result.put("balance", vl.getAmount((String) response.get("currbal")));
-        result.put("history", response.get("history"));
-        result.put("expireDate", response.get("expiredate"));
-        result.put("cardClass", response.get("cardclass"));
-        result.put("referenceNum", response.get("traceno"));
-        Debug.logInfo("History Result : " + result, MODULE);
-        return result;
-
-    }
-
-    public static Map<String, Object> refund(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        Properties props = getProperties(context);
-        String cardNumber = (String) context.get("cardNumber");
-        String pin = (String) context.get("pin");
-        String currency = (String) context.get("currency");
-        String orderId = (String) context.get("orderId");
-        String partyId = (String) context.get("partyId");
-        BigDecimal amount = (BigDecimal) context.get("amount");
-        Locale locale = (Locale) context.get("locale");
-
-        // override interface for void/rollback
-        String iFace = (String) context.get("Interface");
-
-        // get an api instance
-        ValueLinkApi vl = ValueLinkApi.getInstance(delegator, props);
-        Map<String, Object> request = vl.getInitialRequestMap(context);
-        request.put("Interface", iFace != null ? iFace : "Refund");
-        request.put("CardNo", cardNumber);
-        request.put("PIN", vl.encryptPin(pin));
-        request.put("Amount", vl.getAmount(amount));
-        request.put("LocalCurr", vl.getCurrency(currency));
-
-        // user defined field #1
-        if (UtilValidate.isNotEmpty(orderId)) {
-            request.put("User1", orderId);
-        }
-
-        // user defined field #2
-        if (UtilValidate.isNotEmpty(partyId)) {
-            request.put("User2", partyId);
-        }
-
-        // set the timeout reversal
-        setTimeoutReversal(dctx, context, request);
-
-        // send the request
-        Map<String, Object> response = null;
-        try {
-            response = vl.send(request);
-        } catch (HttpClientException e) {
-            Debug.logError(e, "Problem communicating with VL");
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToRefundGiftCard", locale));
-        }
-
-        String responseCode = (String) response.get("responsecode");
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-
-        result.put("processResult", "00".equals(responseCode));
-        result.put("responseCode", responseCode);
-        result.put("authCode", response.get("authcode"));
-        result.put("previousAmount", vl.getAmount((String) response.get("prevbal")));
-        result.put("amount", vl.getAmount((String) response.get("newbal")));
-        result.put("expireDate", response.get("expiredate"));
-        result.put("cardClass", response.get("cardclass"));
-        result.put("referenceNum", response.get("traceno"));
-        Debug.logInfo("Refund Result : " + result, MODULE);
-        return result;
-
-    }
-
-    public static Map<String, Object> voidRedeem(DispatchContext dctx, Map<String, Object> context) {
-        context.put("Interface", "Redeem/Void");
-        return redeem(dctx, context);
-    }
-
-    public static Map<String, Object> voidRefund(DispatchContext dctx, Map<String, Object> context) {
-        context.put("Interface", "Refund/Void");
-        return refund(dctx, context);
-    }
-
-    public static Map<String, Object> voidReload(DispatchContext dctx, Map<String, Object> context) {
-        context.put("Interface", "Reload/Void");
-        return reload(dctx, context);
-    }
-
-    public static Map<String, Object> voidActivate(DispatchContext dctx, Map<String, Object> context) {
-        context.put("Interface", "Activate/Void");
-        return activate(dctx, context);
-    }
-
-    public static Map<String, Object> timeOutReversal(DispatchContext dctx, Map<String, Object> context) {
-        String vlInterface = (String) context.get("Interface");
-        Locale locale = (Locale) context.get("locale");
-        Debug.logInfo("704 Interface : " + vlInterface, MODULE);
-        if (vlInterface != null) {
-            if (vlInterface.startsWith("Activate")) {
-                if ("Activate/Rollback".equals(vlInterface)) {
-                    return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                            "AccountingValueLinkThisTransactionIsNotSupported", locale));
-                }
-                return activate(dctx, context);
-            } else if (vlInterface.startsWith("Redeem")) {
-                return redeem(dctx, context);
-            } else if (vlInterface.startsWith("Reload")) {
-                return reload(dctx, context);
-            } else if (vlInterface.startsWith("Refund")) {
-                return refund(dctx, context);
-            }
-        }
-
-        return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                "AccountingValueLinkTransactionNotValid", locale));
-    }
-
-    // 0704 Timeout Reversal (Supports - Activate/Void, Redeem, Redeem/Void, Reload, Reload/Void, Refund, Refund/Void)
-    private static void setTimeoutReversal(DispatchContext dctx, Map<String, Object> ctx, Map<String, Object> request) {
-        String vlInterface = (String) request.get("Interface");
-        // clone the context
-        Map<String, Object> context = new HashMap<>();
-        context.putAll(ctx);
-
-        // append the rollback interface
-        if (!vlInterface.endsWith("Rollback")) {
-            context.put("Interface", vlInterface + "/Rollback");
-        } else {
-            // no need to re-run ourself we are persisted
-            return;
-        }
-
-        // set the old tx time and number
-        context.put("MerchTime", request.get("MerchTime"));
-        context.put("TermTxnNo", request.get("TermTxnNo"));
-
-        // Activate/Rollback is not supported by valuelink
-        if (!"Activate".equals(vlInterface)) {
-            // create the listener
-            Debug.logInfo("Set 704 context : " + context, MODULE);
-            try {
-                dctx.getDispatcher().addRollbackService("vlTimeOutReversal", context, false);
-                //dctx.getDispatcher().addCommitService("vlTimeOutReversal", context, false);
-            } catch (GenericServiceException e) {
-                Debug.logError(e, "Unable to setup 0704 Timeout Reversal", MODULE);
-            }
-        }
-    }
-
-    private static Properties getProperties(Map<String, Object> context) {
-        String paymentProperties = (String) context.get("paymentConfig");
-        if (paymentProperties == null) {
-            paymentProperties = "payment.properties";
-        }
-        return UtilProperties.getProperties(paymentProperties);
-    }
-
-
-    // payment processing wrappers (process/release/refund)
-
-    public static Map<String, Object> giftCardProcessor(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        LocalDispatcher dispatcher = dctx.getDispatcher();
-        GenericValue userLogin = (GenericValue) context.get("userLogin");
-        Locale locale = (Locale) context.get("locale");
-        GenericValue giftCard = (GenericValue) context.get("giftCard");
-        GenericValue party = (GenericValue) context.get("billToParty");
-        String paymentConfig = (String) context.get("paymentConfig");
-        String currency = (String) context.get("currency");
-        String orderId = (String) context.get("orderId");
-        BigDecimal amount = (BigDecimal) context.get("processAmount");
-
-        // make sure we have a currency
-        if (currency == null) {
-            currency = EntityUtilProperties.getPropertyValue("general", "currency.uom.id.default", "USD", delegator);
-        }
-
-        Map<String, Object> redeemCtx = new HashMap<>();
-        redeemCtx.put("userLogin", userLogin);
-        redeemCtx.put("paymentConfig", paymentConfig);
-        redeemCtx.put("cardNumber", giftCard.get("cardNumber"));
-        redeemCtx.put("pin", giftCard.get("pinNumber"));
-        redeemCtx.put("currency", currency);
-        redeemCtx.put("orderId", orderId);
-        redeemCtx.put("partyId", party.get("partyId"));
-        redeemCtx.put("amount", amount);
-
-        // invoke the redeem service
-        Map<String, Object> redeemResult = null;
-        try {
-            redeemResult = dispatcher.runSync("redeemGiftCard", redeemCtx);
-        } catch (GenericServiceException e) {
-            Debug.logError(e, "Problem calling the redeem service", MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToRedeemGiftCardFailure", locale));
-        }
-
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-        if (redeemResult != null) {
-            Boolean processResult = (Boolean) redeemResult.get("processResult");
-            // confirm the amount redeemed; since VL does not error in insufficient funds
-            if (processResult) {
-                BigDecimal previous = (BigDecimal) redeemResult.get("previousAmount");
-                if (previous == null) previous = BigDecimal.ZERO;
-                BigDecimal current = (BigDecimal) redeemResult.get("amount");
-                if (current == null) current = BigDecimal.ZERO;
-                BigDecimal redeemed = previous.subtract(current);
-                Debug.logInfo("Redeemed (" + amount + "): " + redeemed + " / " + previous + " : " + current, MODULE);
-                if (redeemed.compareTo(amount) < 0) {
-                    // we didn't redeem enough void the transaction and return false
-                    Map<String, Object> voidResult = null;
-                    try {
-                        voidResult = dispatcher.runSync("voidRedeemGiftCard", redeemCtx);
-                    } catch (GenericServiceException e) {
-                        Debug.logError(e, MODULE);
-                    }
-                    if (ServiceUtil.isError(voidResult)) {
-                        return voidResult;
-                    }
-                    processResult = Boolean.FALSE;
-                    amount = redeemed;
-                    result.put("authMessage", "Gift card did not contain enough funds");
-                }
-            }
-            result.put("processAmount", amount);
-            result.put("authFlag", redeemResult.get("responseCode"));
-            result.put("authResult", processResult);
-            result.put("captureResult", processResult);
-            result.put("authCode", redeemResult.get("authCode"));
-            result.put("captureCode", redeemResult.get("authCode"));
-            result.put("authRefNum", redeemResult.get("referenceNum"));
-            result.put("captureRefNum", redeemResult.get("referenceNum"));
-        }
-
-        return result;
-    }
-
-    public static Map<String, Object> giftCardRelease(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        LocalDispatcher dispatcher = dctx.getDispatcher();
-        GenericValue userLogin = (GenericValue) context.get("userLogin");
-        Locale locale = (Locale) context.get("locale");
-        GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference");
-        String paymentConfig = (String) context.get("paymentConfig");
-        String currency = (String) context.get("currency");
-        BigDecimal amount = (BigDecimal) context.get("releaseAmount");
-
-        // get the orderId for tracking
-        String orderId = paymentPref.getString("orderId");
-
-        // get the GiftCard VO
-        GenericValue giftCard = null;
-        try {
-            giftCard = paymentPref.getRelatedOne("GiftCard", false);
-        } catch (GenericEntityException e) {
-            Debug.logError("Unable to get GiftCard from OrderPaymentPreference", MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotLocateItFromOrderPaymentPreference", locale));
-        }
-
-        if (giftCard == null) {
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToReleaseGiftCard", locale));
-        }
-
-        // make sure we have a currency
-        if (currency == null) {
-            currency = EntityUtilProperties.getPropertyValue("general", "currency.uom.id.default", "USD", delegator);
-        }
-
-        Map<String, Object> redeemCtx = new HashMap<>();
-        redeemCtx.put("userLogin", userLogin);
-        redeemCtx.put("paymentConfig", paymentConfig);
-        redeemCtx.put("cardNumber", giftCard.get("cardNumber"));
-        redeemCtx.put("pin", giftCard.get("pinNumber"));
-        redeemCtx.put("currency", currency);
-        redeemCtx.put("orderId", orderId);
-        redeemCtx.put("amount", amount);
-
-        // invoke the void redeem service
-        Map<String, Object> redeemResult = null;
-        try {
-            redeemResult = dispatcher.runSync("voidRedeemGiftCard", redeemCtx);
-        } catch (GenericServiceException e) {
-            Debug.logError(e, "Problem calling the redeem service", MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToRedeemGiftCardFailure", locale));
-        }
-
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-        if (redeemResult != null) {
-            Boolean processResult = (Boolean) redeemResult.get("processResult");
-            result.put("releaseAmount", redeemResult.get("amount"));
-            result.put("releaseFlag", redeemResult.get("responseCode"));
-            result.put("releaseResult", processResult);
-            result.put("releaseCode", redeemResult.get("authCode"));
-            result.put("releaseRefNum", redeemResult.get("referenceNum"));
-        }
-
-        return result;
-    }
-
-    public static Map<String, Object> giftCardRefund(DispatchContext dctx, Map<String, Object> context) {
-        Delegator delegator = dctx.getDelegator();
-        LocalDispatcher dispatcher = dctx.getDispatcher();
-        GenericValue userLogin = (GenericValue) context.get("userLogin");
-        Locale locale = (Locale) context.get("locale");
-        GenericValue paymentPref = (GenericValue) context.get("orderPaymentPreference");
-        String paymentConfig = (String) context.get("paymentConfig");
-        String currency = (String) context.get("currency");
-        BigDecimal amount = (BigDecimal) context.get("refundAmount");
-
-        // get the orderId for tracking
-        String orderId = paymentPref.getString("orderId");
-
-        // get the GiftCard VO
-        GenericValue giftCard = null;
-        try {
-            giftCard = paymentPref.getRelatedOne("GiftCard", false);
-        } catch (GenericEntityException e) {
-            Debug.logError("Unable to get GiftCard from OrderPaymentPreference", MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotLocateItFromOrderPaymentPreference", locale));
-        }
-
-        if (giftCard == null) {
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToReleaseGiftCard", locale));
-        }
-
-        // make sure we have a currency
-        if (currency == null) {
-            currency = EntityUtilProperties.getPropertyValue("general", "currency.uom.id.default", "USD", delegator);
-        }
-
-        Map<String, Object> refundCtx = new HashMap<>();
-        refundCtx.put("userLogin", userLogin);
-        refundCtx.put("paymentConfig", paymentConfig);
-        refundCtx.put("cardNumber", giftCard.get("cardNumber"));
-        refundCtx.put("pin", giftCard.get("pinNumber"));
-        refundCtx.put("currency", currency);
-        refundCtx.put("orderId", orderId);
-        refundCtx.put("amount", amount);
-
-        // invoke the refund service
-        Map<String, Object> redeemResult = null;
-        try {
-            redeemResult = dispatcher.runSync("refundGiftCard", refundCtx);
-        } catch (GenericServiceException e) {
-            Debug.logError(e, "Problem calling the refund service", MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToRefundGiftCardFailure", locale));
-        }
-
-        Map<String, Object> result = ServiceUtil.returnSuccess();
-        if (redeemResult != null) {
-            Boolean processResult = (Boolean) redeemResult.get("processResult");
-            result.put("refundAmount", redeemResult.get("amount"));
-            result.put("refundFlag", redeemResult.get("responseCode"));
-            result.put("refundResult", processResult);
-            result.put("refundCode", redeemResult.get("authCode"));
-            result.put("refundRefNum", redeemResult.get("referenceNum"));
-        }
-
-        return result;
-    }
-
-    // item fulfillment wrappers (purchase/reload)
-
-    public static Map<String, Object> giftCardPurchase(DispatchContext dctx, Map<String, Object> context) {
-        // this service should always be called via FULFILLMENT_EXTASYNC
-        LocalDispatcher dispatcher = dctx.getDispatcher();
-        Delegator delegator = dctx.getDelegator();
-        GenericValue userLogin = (GenericValue) context.get("userLogin");
-        GenericValue orderItem = (GenericValue) context.get("orderItem");
-        Locale locale = (Locale) context.get("locale");
-
-        // order ID for tracking
-        String orderId = orderItem.getString("orderId");
-
-        // the order header for store info
-        GenericValue orderHeader = null;
-        try {
-            orderHeader = orderItem.getRelatedOne("OrderHeader", false);
-        } catch (GenericEntityException e) {
-            Debug.logError(e, "Unable to get OrderHeader from OrderItem", MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ORDER,
-                    "OrderOrderNotFound", UtilMisc.toMap("orderId", orderId), locale));
-        }
-
-        // get the order read helper
-        OrderReadHelper orh = new OrderReadHelper(orderHeader);
-
-        // get the currency
-        String currency = orh.getCurrency();
-
-        // make sure we have a currency
-        if (currency == null) {
-            currency = EntityUtilProperties.getPropertyValue("general", "currency.uom.id.default", "USD", delegator);
-        }
-
-        // get the product store
-        String productStoreId = null;
-        if (orderHeader != null) {
-            productStoreId = orh.getProductStoreId();
-        }
-        if (productStoreId == null) {
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotProcess", locale));
-        }
-
-        // payment config
-        GenericValue paymentSetting = ProductStoreWorker.getProductStorePaymentSetting(delegator, productStoreId, "GIFT_CARD", null, true);
-        String paymentConfig = null;
-        if (paymentSetting != null) {
-            paymentConfig = paymentSetting.getString("paymentPropertiesPath");
-        }
-        if (paymentConfig == null) {
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingFinAccountSetting",
-                    UtilMisc.toMap("productStoreId", productStoreId, "finAccountTypeId", "GIFT_CARD"), locale));
-        }
-
-        // party ID for tracking
-        GenericValue placingParty = orh.getPlacingParty();
-        String partyId = null;
-        if (placingParty != null) {
-            partyId = placingParty.getString("partyId");
-        }
-
-        // amount/quantity of the gift card(s)
-        BigDecimal amount = orderItem.getBigDecimal("unitPrice");
-        BigDecimal quantity = orderItem.getBigDecimal("quantity");
-
-        // the product entity needed for information
-        GenericValue product = null;
-        try {
-            product = orderItem.getRelatedOne("Product", false);
-        } catch (GenericEntityException e) {
-            Debug.logError("Unable to get Product from OrderItem", MODULE);
-        }
-        if (product == null) {
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotFulfill", locale));
-        }
-
-        // get the productFeature type TYPE (VL promo code)
-        GenericValue typeFeature = null;
-        try {
-            typeFeature = EntityQuery.use(delegator)
-                    .from("ProductFeatureAndAppl")
-                    .where("productId", product.get("productId"),
-                            "productFeatureTypeId", "TYPE")
-                    .orderBy("-fromDate").filterByDate().queryFirst();
-        } catch (GenericEntityException e) {
-            Debug.logError(e, MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToGetFeatureType", locale));
-        }
-        if (typeFeature == null) {
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkFeatureTypeRequested",
-                    UtilMisc.toMap("productId", product.get("productId")), locale));
-        }
-
-        // get the VL promo code
-        String promoCode = typeFeature.getString("idCode");
-        if (UtilValidate.isEmpty(promoCode)) {
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkPromoCodeInvalid", locale));
-        }
-
-        // survey information
-        String surveyId = EntityUtilProperties.getPropertyValue(paymentConfig, "payment.giftcert.purchase.surveyId", delegator);
-
-        // get the survey response
-        GenericValue surveyResponse = null;
-        try {
-            surveyResponse = EntityQuery.use(delegator).from("SurveyResponse")
-                    .where("orderId", orderId,
-                            "orderItemSeqId", orderItem.get("orderItemSeqId"),
-                            "surveyId", surveyId)
-                    .queryFirst();
-        } catch (GenericEntityException e) {
-            Debug.logError(e, MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotFulfillFromSurvey", locale));
-        }
-
-        // get the response answers
-        List<GenericValue> responseAnswers = null;
-        try {
-            responseAnswers = surveyResponse.getRelated("SurveyResponseAnswer", null, null, false);
-        } catch (GenericEntityException e) {
-            Debug.logError(e, MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotFulfillFromSurveyAnswers", locale));
-        }
-
-        // make a map of answer info
-        Map<String, Object> answerMap = new HashMap<>();
-        if (responseAnswers != null) {
-            for (GenericValue answer : responseAnswers) {
-                GenericValue question = null;
-                try {
-                    question = answer.getRelatedOne("SurveyQuestion", false);
-                } catch (GenericEntityException e) {
-                    Debug.logError(e, MODULE);
-                    return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                            "AccountingGiftCertificateNumberCannotFulfillFromSurveyAnswers", locale));
-                }
-                if (question != null) {
-                    String desc = question.getString("description");
-                    String ans = answer.getString("textResponse");  // only support text response types for now
-                    answerMap.put(desc, ans);
-                }
-            }
-        }
-
-        // get the send to email address - key defined in properties file
-        String sendToKey = EntityUtilProperties.getPropertyValue(paymentConfig, "payment.giftcert.purchase.survey.sendToEmail", delegator);
-        String sendToEmail = (String) answerMap.get(sendToKey);
-        // get the copyMe flag and set the order email address
-        String orderEmails = orh.getOrderEmailString();
-        String copyMeField = EntityUtilProperties.getPropertyValue(paymentConfig, "payment.giftcert.purchase.survey.copyMe", delegator);
-        String copyMeResp = copyMeField != null ? (String) answerMap.get(copyMeField) : null;
-        boolean copyMe = UtilValidate.isNotEmpty(copyMeField)
-                && UtilValidate.isNotEmpty(copyMeResp) && "true".equalsIgnoreCase(copyMeResp);
-
-        int qtyLoop = quantity.intValue();
-        for (int i = 0; i < qtyLoop; i++) {
-            // activate a gift card
-            Map<String, Object> activateCtx = new HashMap<>();
-            activateCtx.put("paymentConfig", paymentConfig);
-            activateCtx.put("vlPromoCode", promoCode);
-            activateCtx.put("currency", currency);
-            activateCtx.put("partyId", partyId);
-            activateCtx.put("orderId", orderId);
-            activateCtx.put("amount", amount);
-            activateCtx.put("userLogin", userLogin);
-
-            boolean failure = false;
-            Map<String, Object> activateResult = null;
-            try {
-                activateResult = dispatcher.runSync("activateGiftCard", activateCtx);
-            } catch (GenericServiceException e) {
-                Debug.logError(e, "Unable to activate gift card(s)", MODULE);
-                return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                        "AccountingValueLinkUnableToActivateGiftCard", locale));
-            }
-
-            Boolean processResult = (Boolean) activateResult.get("processResult");
-            if (activateResult.containsKey(ModelService.ERROR_MESSAGE) || !processResult) {
-                failure = true;
-            }
-
-            if (!failure) {
-                // set the void on rollback
-                try {
-                    dispatcher.addRollbackService("voidActivateGiftCard", activateCtx, false);
-                } catch (GenericServiceException e) {
-                    Debug.logError(e, "Unable to setup Activate/Void on error", MODULE);
-                }
-            }
-
-            // create the fulfillment record
-            Map<String, Object> vlFulFill = new HashMap<>();
-            vlFulFill.put("typeEnumId", "GC_ACTIVATE");
-            vlFulFill.put("merchantId", EntityUtilProperties.getPropertyValue(paymentConfig, "payment.valuelink.merchantId", delegator));
-            vlFulFill.put("partyId", partyId);
-            vlFulFill.put("orderId", orderId);
-            vlFulFill.put("orderItemSeqId", orderItem.get("orderItemSeqId"));
-            vlFulFill.put("surveyResponseId", surveyResponse.get("surveyResponseId"));
-            vlFulFill.put("cardNumber", activateResult.get("cardNumber"));
-            vlFulFill.put("pinNumber", activateResult.get("pin"));
-            vlFulFill.put("amount", activateResult.get("amount"));
-            vlFulFill.put("responseCode", activateResult.get("responseCode"));
-            vlFulFill.put("referenceNum", activateResult.get("referenceNum"));
-            vlFulFill.put("authCode", activateResult.get("authCode"));
-            vlFulFill.put("userLogin", userLogin);
-            try {
-                dispatcher.runAsync("createGcFulFillmentRecord", vlFulFill, true);
-            } catch (GenericServiceException e) {
-                Debug.logError(e, MODULE);
-                return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                        "AccountingGiftCertificateNumberCannotStoreFulfillmentInfo",
-                        UtilMisc.toMap("errorString", e.toString()), locale));
-            }
-
-            if (failure) {
-                return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                        "AccountingValueLinkUnableToActivateGiftCard", locale));
-            }
-
-            // add some information to the answerMap for the email
-            answerMap.put("cardNumber", activateResult.get("cardNumber"));
-            answerMap.put("pinNumber", activateResult.get("pin"));
-            answerMap.put("amount", activateResult.get("amount"));
-
-            // get the email setting for this email type
-            GenericValue productStoreEmail = null;
-            String emailType = "PRDS_GC_PURCHASE";
-            try {
-                productStoreEmail = EntityQuery.use(delegator).from("ProductStoreEmailSetting").where("productStoreId", productStoreId,
-                        "emailType", emailType).queryOne();
-            } catch (GenericEntityException e) {
-                Debug.logError(e, "Unable to get product store email setting for gift card purchase", MODULE);
-            }
-            if (productStoreEmail == null) {
-                Debug.logError("No gift card purchase email setting found for this store; cannot send gift card information", MODULE);
-            } else {
-                answerMap.put("locale", locale);
-
-                // set the bcc address(s)
-                String bcc = productStoreEmail.getString("bccAddress");
-                if (copyMe) {
-                    if (UtilValidate.isNotEmpty(bcc)) {
-                        bcc = bcc + "," + orderEmails;
-                    } else {
-                        bcc = orderEmails;
-                    }
-                }
-
-                Map<String, Object> emailCtx = new HashMap<>();
-                emailCtx.put("bodyScreenUri", productStoreEmail.getString("bodyScreenLocation"));
-                emailCtx.put("bodyParameters", answerMap);
-                emailCtx.put("sendTo", sendToEmail);
-                emailCtx.put("contentType", productStoreEmail.get("contentType"));
-                emailCtx.put("sendFrom", productStoreEmail.get("fromAddress"));
-                emailCtx.put("sendCc", productStoreEmail.get("ccAddress"));
-                emailCtx.put("sendBcc", bcc);
-                emailCtx.put("subject", productStoreEmail.getString("subject"));
-                emailCtx.put("userLogin", userLogin);
-
-                // send off the email async so we will retry on failed attempts
-                try {
-                    dispatcher.runAsync("sendMailFromScreen", emailCtx);
-                } catch (GenericServiceException e) {
-                    Debug.logError(e, "Problem sending mail", MODULE);
-                    // this is fatal; we will rollback and try again later
-                    return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                            "AccountingGiftCertificateNumberCannotSendEmailNotice",
-                            UtilMisc.toMap("errorString", e.toString()), locale));
-                }
-            }
-        }
-
-        return ServiceUtil.returnSuccess();
-    }
-
-    public static Map<String, Object> giftCardReload(DispatchContext dctx, Map<String, Object> context) {
-        // this service should always be called via FULFILLMENT_EXTSYNC
-        LocalDispatcher dispatcher = dctx.getDispatcher();
-        Delegator delegator = dctx.getDelegator();
-        GenericValue userLogin = (GenericValue) context.get("userLogin");
-        GenericValue orderItem = (GenericValue) context.get("orderItem");
-        Locale locale = (Locale) context.get("locale");
-
-        // order ID for tracking
-        String orderId = orderItem.getString("orderId");
-
-        // the order header for store info
-        GenericValue orderHeader = null;
-        try {
-            orderHeader = orderItem.getRelatedOne("OrderHeader", false);
-        } catch (GenericEntityException e) {
-            Debug.logError(e, "Unable to get OrderHeader from OrderItem", MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ORDER,
-                    "OrderOrderNotFound", UtilMisc.toMap("orderId", orderId), locale));
-        }
-
-        // get the order read helper
-        OrderReadHelper orh = new OrderReadHelper(orderHeader);
-
-        // get the currency
-        String currency = orh.getCurrency();
-
-        // make sure we have a currency
-        if (currency == null) {
-            currency = EntityUtilProperties.getPropertyValue("general", "currency.uom.id.default", "USD", delegator);
-        }
-
-        // get the product store
-        String productStoreId = null;
-        if (orderHeader != null) {
-            productStoreId = orh.getProductStoreId();
-        }
-        if (productStoreId == null) {
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotProcess",
-                    UtilMisc.toMap("orderId", orderId), locale));
-        }
-
-        // payment config
-        GenericValue paymentSetting = ProductStoreWorker.getProductStorePaymentSetting(delegator, productStoreId, "GIFT_CARD", null, true);
-        String paymentConfig = null;
-        if (paymentSetting != null) {
-            paymentConfig = paymentSetting.getString("paymentPropertiesPath");
-        }
-        if (paymentConfig == null) {
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotGetPaymentConfiguration", locale));
-        }
-
-        // party ID for tracking
-        GenericValue placingParty = orh.getPlacingParty();
-        String partyId = null;
-        if (placingParty != null) {
-            partyId = placingParty.getString("partyId");
-        }
-
-        // amount of the gift card reload
-        BigDecimal amount = orderItem.getBigDecimal("unitPrice");
-
-        // survey information
-        String surveyId = EntityUtilProperties.getPropertyValue(paymentConfig, "payment.giftcert.reload.surveyId", delegator);
-
-        // get the survey response
-        GenericValue surveyResponse = null;
-        try {
-            surveyResponse = EntityQuery.use(delegator).from("SurveyResponse")
-                    .where("orderId", orderId,
-                            "orderItemSeqId", orderItem.get("orderItemSeqId"),
-                            "surveyId", surveyId).orderBy("-responseDate")
-                    .queryFirst();
-        } catch (GenericEntityException e) {
-            Debug.logError(e, MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotFulfillFromSurvey", locale));
-        }
-
-        // get the response answers
-        List<GenericValue> responseAnswers = null;
-        try {
-            responseAnswers = surveyResponse.getRelated("SurveyResponseAnswer", null, null, false);
-        } catch (GenericEntityException e) {
-            Debug.logError(e, MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotFulfillFromSurveyAnswers", locale));
-        }
-
-        // make a map of answer info
-        Map<String, Object> answerMap = new HashMap<>();
-        if (responseAnswers != null) {
-            for (GenericValue answer : responseAnswers) {
-                GenericValue question = null;
-                try {
-                    question = answer.getRelatedOne("SurveyQuestion", false);
-                } catch (GenericEntityException e) {
-                    Debug.logError(e, MODULE);
-                    return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                            "AccountingGiftCertificateNumberCannotFulfillFromSurveyAnswers", locale));
-                }
-                if (question != null) {
-                    String desc = question.getString("description");
-                    String ans = answer.getString("textResponse");  // only support text response types for now
-                    answerMap.put(desc, ans);
-                }
-            }
-        }
-
-        String cardNumberKey = EntityUtilProperties.getPropertyValue(paymentConfig, "payment.giftcert.reload.survey.cardNumber", delegator);
-        String pinNumberKey = EntityUtilProperties.getPropertyValue(paymentConfig, "payment.giftcert.reload.survey.pinNumber", delegator);
-        String cardNumber = (String) answerMap.get(cardNumberKey);
-        String pinNumber = (String) answerMap.get(pinNumberKey);
-
-        // reload the gift card
-        Map<String, Object> reloadCtx = new HashMap<>();
-        reloadCtx.put("paymentConfig", paymentConfig);
-        reloadCtx.put("currency", currency);
-        reloadCtx.put("partyId", partyId);
-        reloadCtx.put("orderId", orderId);
-        reloadCtx.put("cardNumber", cardNumber);
-        reloadCtx.put("pin", pinNumber);
-        reloadCtx.put("amount", amount);
-        reloadCtx.put("userLogin", userLogin);
-
-        Map<String, Object> reloadResult = null;
-        try {
-            reloadResult = dispatcher.runSync("reloadGiftCard", reloadCtx);
-        } catch (GenericServiceException e) {
-            Debug.logError(e, "Unable to reload gift card", MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                    "AccountingValueLinkUnableToReloadGiftCard", locale));
-        }
-
-        // create the fulfillment record
-        Map<String, Object> vlFulFill = new HashMap<>();
-        vlFulFill.put("typeEnumId", "GC_RELOAD");
-        vlFulFill.put("merchantId", EntityUtilProperties.getPropertyValue(paymentConfig, "payment.valuelink.merchantId", delegator));
-        vlFulFill.put("partyId", partyId);
-        vlFulFill.put("orderId", orderId);
-        vlFulFill.put("orderItemSeqId", orderItem.get("orderItemSeqId"));
-        vlFulFill.put("surveyResponseId", surveyResponse.get("surveyResponseId"));
-        vlFulFill.put("cardNumber", cardNumber);
-        vlFulFill.put("pinNumber", pinNumber);
-        vlFulFill.put("amount", amount);
-        vlFulFill.put("responseCode", reloadResult.get("responseCode"));
-        vlFulFill.put("referenceNum", reloadResult.get("referenceNum"));
-        vlFulFill.put("authCode", reloadResult.get("authCode"));
-        vlFulFill.put("userLogin", userLogin);
-        try {
-            dispatcher.runAsync("createGcFulFillmentRecord", vlFulFill, true);
-        } catch (GenericServiceException e) {
-            Debug.logError(e, MODULE);
-            return ServiceUtil.returnError(UtilProperties.getMessage(RES_ERROR,
-                    "AccountingGiftCertificateNumberCannotStoreFulfillmentInfo", locale));
-        }
-
-        Boolean processResult = (Boolean) reloadResult.get("processResult");
-        if (reloadResult.containsKey(ModelService.ERROR_MESSAGE) || !processResult) {
-            Debug.logError("Reload Failed Need to Refund : " + reloadResult, MODULE);
-
-            // process the return
-            try {
-                Map<String, Object> refundCtx = UtilMisc.<String, Object>toMap("orderItem", orderItem,
-                        "partyId", partyId, "userLogin", userLogin);
-                dispatcher.runAsync("refundGcPurchase", refundCtx, null, true, 300, true);
-            } catch (GenericServiceException e) {
-                Debug.logError(e, "ERROR! Unable to call create refund service; this failed reload will NOT be refunded", MODULE);
-            }
-
-            String responseCode = "-1";
-            if (processResult != null) {
-                responseCode = (String) reloadResult.get("responseCode");
-            }
-            if ("17".equals(responseCode)) {
-                Debug.logError("Error code : " + responseCode + " : Max Balance Exceeded", MODULE);
-                return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                        "AccountingValueLinkUnableToRefundGiftCardMaxBalanceExceeded", locale));
-            } else {
-                Debug.logError("Error code : " + responseCode + " : Processing Error", MODULE);
-                return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                        "AccountingValueLinkUnableToReloadGiftCardFailed", locale));
-            }
-        }
-
-        // add some information to the answerMap for the email
-        answerMap.put("processResult", reloadResult.get("processResult"));
-        answerMap.put("responseCode", reloadResult.get("responseCode"));
-        answerMap.put("previousAmount", reloadResult.get("previousAmount"));
-        answerMap.put("amount", reloadResult.get("amount"));
-
-        // get the email setting for this email type
-        GenericValue productStoreEmail = null;
-        String emailType = "PRDS_GC_RELOAD";
-        try {
-            productStoreEmail = EntityQuery.use(delegator).from("ProductStoreEmailSetting").where("productStoreId", productStoreId, "emailType",
-                    emailType).queryOne();
-        } catch (GenericEntityException e) {
-            Debug.logError(e, "Unable to get product store email setting for gift card purchase", MODULE);
-        }
-        if (productStoreEmail == null) {
-            Debug.logError("No gift card purchase email setting found for this store; cannot send gift card information", MODULE);
-        } else {
-            Map<String, Object> emailCtx = new HashMap<>();
-            answerMap.put("locale", locale);
-
-            emailCtx.put("bodyScreenUri", productStoreEmail.getString("bodyScreenLocation"));
-            emailCtx.put("bodyParameters", answerMap);
-            emailCtx.put("sendTo", orh.getOrderEmailString());
-            emailCtx.put("contentType", productStoreEmail.get("contentType"));
-            emailCtx.put("sendFrom", productStoreEmail.get("fromAddress"));
-            emailCtx.put("sendCc", productStoreEmail.get("ccAddress"));
-            emailCtx.put("sendBcc", productStoreEmail.get("bccAddress"));
-            emailCtx.put("subject", productStoreEmail.getString("subject"));
-            emailCtx.put("userLogin", userLogin);
-
-            // send off the email async so we will retry on failed attempts
-            try {
-                dispatcher.runAsync("sendMailFromScreen", emailCtx);
-            } catch (GenericServiceException e) {
-                Debug.logError(e, "Problem sending mail", MODULE);
-                // this is fatal; we will rollback and try again later
-                return ServiceUtil.returnError(UtilProperties.getMessage(RESOURCE,
-                        "AccountingGiftCertificateNumberCannotSendEmailNotice",
-                        UtilMisc.toMap("errorString", e.toString()), locale));
-            }
-        }
-
-        return ServiceUtil.returnSuccess();
-    }
-}
diff --git a/applications/datamodel/DATAMODEL_CHANGES.adoc b/applications/datamodel/DATAMODEL_CHANGES.adoc
index 71908c1d5d..024a6486db 100644
--- a/applications/datamodel/DATAMODEL_CHANGES.adoc
+++ b/applications/datamodel/DATAMODEL_CHANGES.adoc
@@ -437,7 +437,6 @@ No changes
 |UserLogin |externalAuthId |Added |No |NA
 |UserLogin |userLdapDn |Added |No |NA
 |UserLogin |disabledBy |Added |No |NA
-|ValueLinkKey |createdByUserLogin |Added |No |NA
 |WebSite |visualThemeSetId |Added |No |NA
 |WebSite |hostedPathAlias |Added |No |NA
 |WebSite |isDefault |Added |No |NA
diff --git a/applications/datamodel/data/seed/AccountingSeedData.xml b/applications/datamodel/data/seed/AccountingSeedData.xml
index 7436f603ab..b00c8d3055 100644
--- a/applications/datamodel/data/seed/AccountingSeedData.xml
+++ b/applications/datamodel/data/seed/AccountingSeedData.xml
@@ -836,13 +836,10 @@ under the License.
     <CustomMethod customMethodId="GIFT_AUTH_OFBIZ" customMethodTypeId="GIFT_AUTH" customMethodName="ofbGcAuthorize" description="GIFT card authorize"/>
     <CustomMethod customMethodId="GIFT_AUTH_ALWAYSAPPR" customMethodTypeId="GIFT_AUTH" customMethodName="alwaysApproveGCProcessor" description="GIFT card always authorize"/>
     <CustomMethod customMethodId="GIFT_AUTH_ALWAYSDECL" customMethodTypeId="GIFT_AUTH" customMethodName="alwaysDeclineGCProcessor" description="GIFT card always decline"/>
-    <CustomMethod customMethodId="GIFT_AUTH_VALUELINK" customMethodTypeId="GIFT_AUTH" customMethodName="valueLinkProcessor" description="GIFT card ValueLink authorize"/>
     <CustomMethod customMethodId="GIFT_CAPTURE_OFBIZ" customMethodTypeId="GIFT_CAPTURE" customMethodName="ofbGcProcessor" description="GIFT card capture"/>
     <CustomMethod customMethodId="GIFT_REFUND_OFBIZ" customMethodTypeId="GIFT_REFUND" customMethodName="ofbGcRefund" description="GIFT card refund"/>
-    <CustomMethod customMethodId="GIFT_REFUND_VALUELI" customMethodTypeId="GIFT_REFUND" customMethodName="valueLinkRefund" description="GIFT card ValueLink refund"/>
     <CustomMethod customMethodId="GIFT_RELEASE_OFBIZ" customMethodTypeId="GIFT_RELEASE" customMethodName="ofbGcRelease" description="GIFT card release"/>
     <CustomMethod customMethodId="GIFT_RELEASE_TEST" customMethodTypeId="GIFT_RELEASE" customMethodName="testGCRelease" description="GIFT card test release"/>
-    <CustomMethod customMethodId="GIFT_RELEASE_VALUEL" customMethodTypeId="GIFT_RELEASE" customMethodName="valueLinkRelease" description="GIFT card ValueLink release"/>
     <CustomMethod customMethodId="GIFT_PURCH_OFBIZ" customMethodTypeId="GIFT_PURCHASE" customMethodName="ofbGcPurchase" description="GIFT card Purchase Fulfillment Service"/>
 
     <CustomMethod customMethodId="PAYPAL_AUTH_PFP" customMethodTypeId="PAYPAL_AUTH" customMethodName="payflowPayPalProcessor" description="Payflow PayPal payment authorize"/>
diff --git a/applications/datamodel/entitydef/accounting-entitymodel.xml b/applications/datamodel/entitydef/accounting-entitymodel.xml
index 8e5d66623a..9430c70b3c 100644
--- a/applications/datamodel/entitydef/accounting-entitymodel.xml
+++ b/applications/datamodel/entitydef/accounting-entitymodel.xml
@@ -3835,24 +3835,6 @@ under the License.
       </relation>
     </entity>
 
-    <entity entity-name="ValueLinkKey"
-            package-name="org.apache.ofbiz.accounting.payment"
-            title="Value Link Key Store">
-      <field name="merchantId" type="id-vlong"></field>
-      <field name="publicKey" type="very-long"></field>
-      <field name="privateKey" type="very-long"></field>
-      <field name="exchangeKey" type="very-long"></field>
-      <field name="workingKey" type="very-long"></field>
-      <field name="workingKeyIndex" type="numeric"></field>
-      <field name="lastWorkingKey" type="very-long"></field>
-      <field name="createdDate" type="date-time"></field>
-      <field name="createdByTerminal" type="short-varchar"></field>
-      <field name="createdByUserLogin" type="id-vlong"></field>
-      <field name="lastModifiedDate" type="date-time"></field>
-      <field name="lastModifiedByTerminal" type="short-varchar"></field>
-      <field name="lastModifiedByUserLogin" type="id-vlong"></field>
-      <prim-key field="merchantId"/>
-    </entity>
     <!-- ========================================================= -->
     <!-- org.apache.ofbiz.accounting.tax -->
     <!-- ========================================================= -->
diff --git a/applications/order/src/main/java/org/apache/ofbiz/order/shoppingcart/CheckOutHelper.java b/applications/order/src/main/java/org/apache/ofbiz/order/shoppingcart/CheckOutHelper.java
index 2789302bc5..42301056f8 100644
--- a/applications/order/src/main/java/org/apache/ofbiz/order/shoppingcart/CheckOutHelper.java
+++ b/applications/order/src/main/java/org/apache/ofbiz/order/shoppingcart/CheckOutHelper.java
@@ -1920,16 +1920,6 @@ public class CheckOutHelper {
                         Debug.logError(ServiceUtil.getErrorMessage(gcBalanceMap), MODULE);
                     }
                 }
-                if ("valuelink".equalsIgnoreCase(giftCardType)) {
-                    balanceField = "balance";
-                    ctx.put("paymentConfig", paymentConfig);
-                    ctx.put("cardNumber", gc.getString("cardNumber"));
-                    ctx.put("pin", gc.getString("pinNumber"));
-                    gcBalanceMap = dispatcher.runSync("balanceInquireGiftCard", ctx);
-                    if (ServiceUtil.isError(gcBalanceMap)) {
-                        Debug.logError(ServiceUtil.getErrorMessage(gcBalanceMap), MODULE);
-                    }
-                }
             } catch (GenericServiceException e) {
                 Debug.logError(e, MODULE);
             }
diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilValidate.java b/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilValidate.java
index 3be7d81ba2..d0f86e8698 100644
--- a/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilValidate.java
+++ b/framework/base/src/main/java/org/apache/ofbiz/base/util/UtilValidate.java
@@ -920,18 +920,6 @@ public final class UtilValidate {
         return isTime(hour, minute, second);
     }
 
-    /** Check to see if a card number is a valid ValueLink Gift Card
-     * @param stPassed a string representing a valuelink gift card
-     * @return true, if the number passed simple checks
-     */
-    public static boolean isValueLinkCard(String stPassed) {
-        if (isEmpty(stPassed)) {
-            return DEFAULT_EMPTY_OK;
-        }
-        String st = stripCharsInBag(stPassed, CREDIT_CARD_DELIMITERS);
-        return st.length() == 16 && (st.startsWith("7") || st.startsWith("6"));
-    }
-
     /** Check to see if a card number is a valid OFB Gift Card (Certifiicate)
      * @param stPassed a string representing a gift card
      * @return tru, if the number passed simple checks
@@ -949,7 +937,7 @@ public final class UtilValidate {
      * @return true, if the number passed simple checks
      */
     public static boolean isGiftCard(String stPassed) {
-        return isOFBGiftCard(stPassed) || isValueLinkCard(stPassed);
+        return isOFBGiftCard(stPassed);
     }
 
     public static int getLuhnSum(String stPassed) {
diff --git a/spotbugs/exclude.xml b/spotbugs/exclude.xml
index de48b2557b..0f6d61b833 100644
--- a/spotbugs/exclude.xml
+++ b/spotbugs/exclude.xml
@@ -48,11 +48,6 @@ under the License.
     <Method name="getFactoryInstance" />
     <Bug pattern="DC_DOUBLECHECK" />
   </Match>
-  <Match>
-    <Class name="org.apache.ofbiz.accounting.thirdparty.valuelink.ValueLinkApi" />
-    <Method name="getWorkingKeyIndex" />
-    <Bug pattern="DC_DOUBLECHECK" />
-  </Match>
 <!-- ^ ^ OK because of synchronized use -->
 
 <!-- v v OK see remark about OWASP advice at bottom of OFBIZ-9973 -->