1+ /*
2+ * POC: Simulates the Evervault control plugin for Kill Bill.
3+ *
4+ * This plugin implements PaymentControlPluginApi ONLY.
5+ * It intercepts two moments:
6+ *
7+ * 1. addPaymentMethodWithControl — stores the encrypted PAN (ev:... token)
8+ * in an in-memory map, then returns adjustedPluginName to re-tag
9+ * the payment method under the target PSP plugin (e.g. killbill-stripe).
10+ *
11+ * 2. purchasePayment / authorizePayment — retrieves the stored PAN
12+ * and injects it as ccNumber in the plugin properties so the
13+ * PSP plugin can use it.
14+ *
15+ * In production, the in-memory map would be replaced by a database table.
16+ */
17+
18+ package org .killbill .billing .plugin .helloworld ;
19+
20+ import java .util .ArrayList ;
21+ import java .util .List ;
22+ import java .util .UUID ;
23+ import java .util .concurrent .ConcurrentHashMap ;
24+
25+ import org .killbill .billing .control .plugin .api .OnFailurePaymentControlResult ;
26+ import org .killbill .billing .control .plugin .api .OnSuccessPaymentControlResult ;
27+ import org .killbill .billing .control .plugin .api .PaymentControlApiException ;
28+ import org .killbill .billing .control .plugin .api .PaymentControlContext ;
29+ import org .killbill .billing .control .plugin .api .PaymentControlPluginApi ;
30+ import org .killbill .billing .control .plugin .api .PriorPaymentControlResult ;
31+ import org .killbill .billing .payment .api .PluginProperty ;
32+ import org .slf4j .Logger ;
33+ import org .slf4j .LoggerFactory ;
34+
35+ public class HelloWorldPaymentControlPluginApi implements PaymentControlPluginApi {
36+
37+ private static final Logger logger = LoggerFactory .getLogger (HelloWorldPaymentControlPluginApi .class );
38+
39+ // In-memory storage: accountId -> stored card data
40+ // In production, this would be a database table keyed by paymentMethodId
41+ private final ConcurrentHashMap <UUID , StoredCardData > cardStore = new ConcurrentHashMap <>();
42+
43+ private static class StoredCardData {
44+ final String encPan ;
45+ final String encCvv ;
46+ final String expirationMonth ;
47+ final String expirationYear ;
48+ final String targetPlugin ;
49+
50+ StoredCardData (String encPan , String encCvv , String expirationMonth ,
51+ String expirationYear , String targetPlugin ) {
52+ this .encPan = encPan ;
53+ this .encCvv = encCvv ;
54+ this .expirationMonth = expirationMonth ;
55+ this .expirationYear = expirationYear ;
56+ this .targetPlugin = targetPlugin ;
57+ }
58+ }
59+
60+ @ Override
61+ public PriorPaymentControlResult priorCall (final PaymentControlContext context ,
62+ final Iterable <PluginProperty > properties )
63+ throws PaymentControlApiException {
64+
65+ final String encPan = getPluginProperty (properties , "encPan" );
66+
67+ if (encPan != null ) {
68+ return handleAddPaymentMethod (context , properties , encPan );
69+ } else {
70+ return handlePayment (context , properties );
71+ }
72+ }
73+
74+ private PriorPaymentControlResult handleAddPaymentMethod (
75+ final PaymentControlContext context ,
76+ final Iterable <PluginProperty > properties ,
77+ final String encPan ) {
78+
79+ final String encCvv = getPluginProperty (properties , "encCvv" );
80+ final String expMonth = getPluginProperty (properties , "expirationMonth" );
81+ final String expYear = getPluginProperty (properties , "expirationYear" );
82+ final String targetPlugin = getPluginProperty (properties , "targetPlugin" );
83+
84+ final UUID accountId = context .getAccountId ();
85+
86+ final StoredCardData card = new StoredCardData (encPan , encCvv , expMonth , expYear , targetPlugin );
87+ cardStore .put (accountId , card );
88+
89+ logger .info ("[Evervault POC] Stored encrypted PAN for accountId={}. Returning adjustedPluginName={}" , accountId , targetPlugin );
90+
91+ return new PriorPaymentControlResult () {
92+ @ Override
93+ public boolean isAborted () { return false ; }
94+
95+ @ Override
96+ public java .math .BigDecimal getAdjustedAmount () { return null ; }
97+
98+ @ Override
99+ public org .killbill .billing .catalog .api .Currency getAdjustedCurrency () { return null ; }
100+
101+ @ Override
102+ public UUID getAdjustedPaymentMethodId () { return null ; }
103+
104+ @ Override
105+ public Iterable <PluginProperty > getAdjustedPluginProperties () { return properties ; }
106+
107+ @ Override
108+ public String getAdjustedPluginName () { return targetPlugin ; }
109+ };
110+ }
111+
112+ private PriorPaymentControlResult handlePayment (
113+ final PaymentControlContext context ,
114+ final Iterable <PluginProperty > properties ) {
115+
116+ final UUID accountId = context .getAccountId ();
117+ final StoredCardData card = cardStore .get (accountId );
118+
119+ if (card == null ) {
120+ logger .info ("[Evervault POC] No stored card for accountId={}. Passing through." , accountId );
121+ return new DefaultPriorPaymentControlResult (properties );
122+ }
123+
124+ logger .info ("[Evervault POC] Injecting encrypted PAN for accountId={}. TransactionType={}" , accountId , context .getTransactionType ());
125+
126+ final List <PluginProperty > adjustedProperties = new ArrayList <>();
127+ for (PluginProperty prop : properties ) {
128+ adjustedProperties .add (prop );
129+ }
130+ adjustedProperties .add (new PluginProperty ("ccNumber" , card .encPan , false ));
131+ adjustedProperties .add (new PluginProperty ("ccExpirationMonth" , card .expirationMonth , false ));
132+ adjustedProperties .add (new PluginProperty ("ccExpirationYear" , card .expirationYear , false ));
133+ if (card .encCvv != null ) {
134+ adjustedProperties .add (new PluginProperty ("ccVerificationValue" , card .encCvv , false ));
135+ }
136+
137+ return new DefaultPriorPaymentControlResult (adjustedProperties );
138+ }
139+
140+ @ Override
141+ public OnSuccessPaymentControlResult onSuccessCall (final PaymentControlContext context ,
142+ final Iterable <PluginProperty > properties )
143+ throws PaymentControlApiException {
144+ logger .info ("[Evervault POC] onSuccessCall for accountId={}" , context .getAccountId ());
145+ return new OnSuccessPaymentControlResult () {
146+ @ Override
147+ public Iterable <PluginProperty > getAdjustedPluginProperties () { return properties ; }
148+ };
149+ }
150+
151+ @ Override
152+ public OnFailurePaymentControlResult onFailureCall (final PaymentControlContext context ,
153+ final Iterable <PluginProperty > properties )
154+ throws PaymentControlApiException {
155+ logger .info ("[Evervault POC] onFailureCall for accountId={}" , context .getAccountId ());
156+ return new OnFailurePaymentControlResult () {
157+ @ Override
158+ public org .joda .time .DateTime getNextRetryDate () { return null ; }
159+
160+ @ Override
161+ public Iterable <PluginProperty > getAdjustedPluginProperties () { return properties ; }
162+ };
163+ }
164+
165+ private String getPluginProperty (final Iterable <PluginProperty > properties , final String key ) {
166+ if (properties == null ) return null ;
167+ for (PluginProperty prop : properties ) {
168+ if (key .equals (prop .getKey ())) {
169+ return prop .getValue () != null ? prop .getValue ().toString () : null ;
170+ }
171+ }
172+ return null ;
173+ }
174+
175+ private static class DefaultPriorPaymentControlResult implements PriorPaymentControlResult {
176+ private final Iterable <PluginProperty > properties ;
177+
178+ DefaultPriorPaymentControlResult (Iterable <PluginProperty > properties ) {
179+ this .properties = properties ;
180+ }
181+
182+ @ Override
183+ public boolean isAborted () { return false ; }
184+
185+ @ Override
186+ public java .math .BigDecimal getAdjustedAmount () { return null ; }
187+
188+ @ Override
189+ public org .killbill .billing .catalog .api .Currency getAdjustedCurrency () { return null ; }
190+
191+ @ Override
192+ public UUID getAdjustedPaymentMethodId () { return null ; }
193+
194+ @ Override
195+ public Iterable <PluginProperty > getAdjustedPluginProperties () { return properties ; }
196+
197+ @ Override
198+ public String getAdjustedPluginName () { return null ; }
199+ }
200+ }
0 commit comments