Adding addons to bundle

264 views
Skip to first unread message

Valentin Crettaz

unread,
May 29, 2014, 5:30:50 AM5/29/14
to killbill...@googlegroups.com
Hi,

I'm currently testing addons. What I'm doing is probably wrong but here goes.

I'm creating a subscription for a BASE product using the Java client createSubscription() method.
Immediately following, I'm creating two more subscriptions for ADDON products using the createSubscription() method as well.
In the addon subscription structureI'm submitting, I set the bundle ID that I got from the entitlement returned from the first call (i.e. the entitlement for the BASE product).
The bundle is created correctly with 1 BASE + 2 ADDON products.

However, as a result of this process, I see three invoices being generated. It looks like the reason is because the InvoiceListener is just reacting immediately to any events coming in and it will create invoices as needed.
Is there a way to not generate invoices until the bundle I'm creating is ready (i.e. with 1 BASE product and two ADDON products)?

Thanks in advance,
Val

Valentin Crettaz

unread,
May 29, 2014, 10:23:41 AM5/29/14
to killbill...@googlegroups.com
I'm wondering if temporarily applying the system tags AUTO_PAY_OFF or AUTO_BILLING_OFF on either Accounts, Bundles or Subscriptions could do the trick.
Sort of like to delimit a "transaction": set tag OFF, create the bundle, add subscriptions, unset tag OFF (and then the billing goes on)
Would something like this work in this case?

Thanks
Val 

stephane brossier

unread,
May 29, 2014, 12:24:19 PM5/29/14
to Valentin Crettaz, killbill...@googlegroups.com
Val,

You are correct that by default Kill Bill reacts to subscription events -- creation of new subscription, cancellation, upgrade/downgrade,... and as a result may trigger additional invoice(s). We created an enhancement task (https://github.com/killbill/killbill/issues/199) to add APIs for creating multiple subscriptions in one call; that way both base plan and add_ons could be created in one call leading to one invoice.

Since we don't support it at the moment, you are left with tagging/untagging account with AUTO_INVOICING_OFF; this is not ideal of course, but i think that should work.

S.
 


--
You received this message because you are subscribed to the Google Groups "Kill Bill users mailing-list" group.
To unsubscribe from this group and stop receiving emails from it, send an email to killbilling-us...@googlegroups.com.
To post to this group, send email to killbill...@googlegroups.com.
Visit this group at http://groups.google.com/group/killbilling-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/killbilling-users/27d7f843-52f6-4050-b105-da1cd979a660%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Valentin Crettaz

unread,
May 30, 2014, 12:12:40 AM5/30/14
to killbill...@googlegroups.com, valentin...@consulthys.com
Thanks Stéphane. I'll be following issue #199, indeed.
I think this should be combined with what @kevinpostlewaite proposes as well, i.e. the ability to delay/postpone the invoice a little. The rationale behind this is that in addition to creating/updating a list of subscriptions, one could also add credit/etc which would also trigger invoices. 

In our case, when setting up a new account, we can add:
- external charges (setup feed)
- one optional credit
- one BASE subscription
- zero or more ADDON subscriptions
Which means that 4 invoices are generated and as a aside effect (if invoice notifications are on) that the customer will receive four emails for each one of the invoices, which is pretty confusing.

So I'm now tagging the account with AUTO_BILLING_OFF before applying any series of changes. Then since invoices are generated asynch, our plugin just has to listen to events saying that an invoice has been generated. That works exactly the way we expected.

Thanks for your insights.
Val

Valentin Crettaz

unread,
May 30, 2014, 1:10:56 AM5/30/14
to killbill...@googlegroups.com, valentin...@consulthys.com
My apologies, of course, la last sentence is not correct since no invoices are being generated, there couldn't possibly be any events signaling invoice creation.
I'll figure out which event suits best for getting the job done.

Val

Valentin Crettaz

unread,
May 30, 2014, 7:05:25 AM5/30/14
to killbill...@googlegroups.com, valentin...@consulthys.com
It turns out there isn't a single event suitable for this, but one way I've found to achieve it is to "memorize" in a custom field at the account level how many subscriptions we're going to create and then have the plugin decrement that custom field on each SUBSCRIPTION_CREATION event. Finally when that counter reaches 0, we can safely remove the AUTO_INVOICING_OFF tag on the account and let the invoicing happen.

There's a small bump in the road, though, when calling removeTag() from the plugin event listener, since that call requires a TAG_CAN_REMOVE permission and there's no authentication/subject context in the event listener, so Shiro complained little. Based on https://shiro.apache.org/subject.html, I'm trying to fake an admin subject to make that call, but I'm not there yet. It seems that Guice (which enhances/proxies the removeTag call) is interfering somehow and killing the fake admin subject I'm setting in the context of the event handling call.

Val

Pierre-Alexandre Meyer

unread,
Jun 2, 2014, 1:28:53 AM6/2/14
to Valentin Crettaz, killbill...@googlegroups.com
Hey Val,

On Fri, May 30, 2014 at 7:05 AM, Valentin Crettaz <valentin...@consulthys.com> wrote:
There's a small bump in the road, though, when calling removeTag() from the plugin event listener, since that call requires a TAG_CAN_REMOVE permission and there's no authentication/subject context in the event listener, so Shiro complained little. Based on https://shiro.apache.org/subject.html, I'm trying to fake an admin subject to make that call, but I'm not there yet. It seems that Guice (which enhances/proxies the removeTag call) is interfering somehow and killing the fake admin subject I'm setting in the context of the event handling call.

Just checking-in -- did you manage to make it work?

--
Pierre

Valentin Crettaz

unread,
Jun 2, 2014, 2:20:03 AM6/2/14
to killbill...@googlegroups.com, valentin...@consulthys.com
Hey,

Thanks for checking in.
Unfortunately not yet.

The code I'm using to fake the subject is the following (taken from https://shiro.apache.org/subject.html) :

        Object userIdentity = "admin";
        String realmName = "admin";
        PrincipalCollection adminPrincipal = new SimplePrincipalCollection(userIdentity, realmName);
        Subject fakeAdmin = new Subject.Builder(new DefaultSecurityManager()).principals(adminPrincipal).authenticated(true).buildSubject();
        fakeAdmin.execute( new Runnable() {
              public void run() { // "step 1" screenshot
                    try {
                            // "step 2" screenshot
                            AdyenPaymentListener.this.osgiKillbillAPI.getTagUserApi().removeTag(accountId, ObjectType.ACCOUNT, ControlTagType.AUTO_INVOICING_OFF.getId(), context);
                    } catch (TagApiException exception) {
                            logService.log(LogService.LOG_ERROR,"Error while switching on invoicing for account " + accountId, exception);
                    }
               }
        });


Then see the two attached screenshots (step1 and step2). 
When entering the runnable (step1) I have the proper subject set/attached to the current thread context. However, when entering removeTag() the subject is defaulted with a null principal (step2). I'm not sure why this is. At this point, I suspect that this is caused by the Guice enhancer not properly propagating the thread context or something, but I'm not 100% sure.

If you have any insights on this, I appreciate.

Val 
step1.png
step2.png

Pierre-Alexandre Meyer

unread,
Jun 2, 2014, 10:34:34 AM6/2/14
to Valentin Crettaz, killbill...@googlegroups.com
On Mon, Jun 2, 2014 at 2:20 AM, Valentin Crettaz <valentin...@consulthys.com> wrote:
When entering the runnable (step1) I have the proper subject set/attached to the current thread context. However, when entering removeTag() the subject is defaulted with a null principal (step2). I'm not sure why this is.
 
Unfortunately, Shiro use of static methods makes the whole plumbing a bit more complicated with OSGI. What's happening is that there are two independent SecurityUtils classes, one seen by the plugin, one by the host.

I have pushed a fix in the osgi-shiro branches (you need to check-out killbill-oss-parent, killbill-api and killbill): the security API is now exported to the bundles and exposes a "login" method, so you should be able to do:

  killbillAPI.getSecurityApi().login("admin", "password");

I tested it using the killbill-hello-world-java-plugin bundle - let me know if that works for you.

--
Pierre

Valentin Crettaz

unread,
Jun 2, 2014, 10:42:50 AM6/2/14
to killbill...@googlegroups.com, valentin...@consulthys.com
Great, thanks!

I'm going to check all that out and see how it's going.

Thanks again,
Val

Valentin Crettaz

unread,
Jun 2, 2014, 11:57:34 AM6/2/14
to killbill...@googlegroups.com, valentin...@consulthys.com
Small issue: it seems that the osgi-shiro branches of the killbill and killbill-api projects don't contain the modifications I made for the new invoice API.

Pierre-Alexandre Meyer

unread,
Jun 2, 2014, 12:13:05 PM6/2/14
to Valentin Crettaz, killbill...@googlegroups.com
On Mon, Jun 2, 2014 at 11:57 AM, Valentin Crettaz <valentin...@consulthys.com> wrote:
Small issue: it seems that the osgi-shiro branches of the killbill and killbill-api projects don't contain the modifications I made for the new invoice API.

Yes, I forked off from master to limit cross-branches dependencies (we have several feature branches in flight at the moment).

--
Pierre

Valentin Crettaz

unread,
Jun 2, 2014, 12:26:02 PM6/2/14
to killbill...@googlegroups.com, valentin...@consulthys.com
OK, so I'm just going to merge that into my local master branch.

Thx

Valentin Crettaz

unread,
Jun 2, 2014, 11:37:42 PM6/2/14
to killbill...@googlegroups.com, valentin...@consulthys.com
Ok, so I merged your osgi-shiro changes into my local repositories of killbill, killbill-api and killbill-oss-parent and rebuilt them all.
That's awesome, it works fine now!

Thanks a lot for your precious help.
Val

Valentin Crettaz

unread,
Jun 4, 2014, 11:21:37 PM6/4/14
to killbill...@googlegroups.com, valentin...@consulthys.com
I've let my KB server run for a while (read overnight) and as I'm resuming testing I'm getting a shiro stacktrace complaining about inactive session.

The stack trace looks like this:
org.apache.shiro.session.UnknownSessionException: There is no session with id [8930]
at org.apache.shiro.session.mgt.eis.AbstractSessionDAO.readSession(AbstractSessionDAO.java:170) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.mgt.eis.CachingSessionDAO.readSession(CachingSessionDAO.java:261) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSessionFromDataSource(DefaultSessionManager.java:236) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.mgt.DefaultSessionManager.retrieveSession(DefaultSessionManager.java:222) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.mgt.AbstractValidatingSessionManager.doGetSession(AbstractValidatingSessionManager.java:118) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.mgt.AbstractNativeSessionManager.lookupSession(AbstractNativeSessionManager.java:108) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.mgt.AbstractNativeSessionManager.lookupRequiredSession(AbstractNativeSessionManager.java:112) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.mgt.AbstractNativeSessionManager.getHost(AbstractNativeSessionManager.java:197) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.mgt.DelegatingSession.getHost(DelegatingSession.java:111) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.ProxiedSession.getHost(ProxiedSession.java:93) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.ProxiedSession.getHost(ProxiedSession.java:93) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.ProxiedSession.getHost(ProxiedSession.java:93) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.ProxiedSession.getHost(ProxiedSession.java:93) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.ProxiedSession.getHost(ProxiedSession.java:93) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.session.ProxiedSession.getHost(ProxiedSession.java:93) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.subject.support.DefaultSubjectContext.resolveHost(DefaultSubjectContext.java:270) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.web.subject.support.DefaultWebSubjectContext.resolveHost(DefaultWebSubjectContext.java:51) ~[shiro-web-1.2.2.jar:1.2.2]
at org.apache.shiro.web.mgt.DefaultWebSubjectFactory.createSubject(DefaultWebSubjectFactory.java:58) ~[shiro-web-1.2.2.jar:1.2.2]
at org.apache.shiro.mgt.DefaultSecurityManager.doCreateSubject(DefaultSecurityManager.java:369) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.mgt.DefaultSecurityManager.createSubject(DefaultSecurityManager.java:344) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.mgt.DefaultSecurityManager.createSubject(DefaultSecurityManager.java:183) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:283) ~[shiro-core-1.2.2.jar:1.2.2]
at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:256) ~[shiro-core-1.2.2.jar:1.2.2]
at org.killbill.billing.util.security.api.DefaultSecurityApi.login(DefaultSecurityApi.java:48) ~[killbill-util-0.11.5-SNAPSHOT.jar:na]
at com.xxx.killbill.plugin.adyen.AdyenPaymentListener.fakeAdminLogin(AdyenPaymentListener.java:189) ~[na:na]
at com.xxx.killbill.plugin.adyen.AdyenPaymentListener.handleKillbillEvent(AdyenPaymentListener.java:163) ~[na:na]
at org.killbill.killbill.osgi.libs.killbill.OSGIKillbillEventDispatcher$1$1.update(OSGIKillbillEventDispatcher.java:62) ~[killbill-osgi-bundles-lib-killbill-0.11.5-SNAPSHOT.jar:na]
at java.util.Observable.notifyObservers(Observable.java:159) ~[na:1.7.0_55]
at org.killbill.billing.osgi.KillbillEventObservable.handleKillbillEvent(KillbillEventObservable.java:60) ~[killbill-osgi-0.11.5-SNAPSHOT.jar:na]
at sun.reflect.GeneratedMethodAccessor202.invoke(Unknown Source) ~[na:na]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.7.0_55]
at java.lang.reflect.Method.invoke(Method.java:606) ~[na:1.7.0_55]
at com.google.common.eventbus.EventHandler.handleEvent(EventHandler.java:74) ~[guava-15.0.jar:na]
at com.google.common.eventbus.SynchronizedEventHandler.handleEvent(SynchronizedEventHandler.java:47) ~[guava-15.0.jar:na]
at com.google.common.eventbus.EventBusThatThrowsException.dispatchWithException(EventBusThatThrowsException.java:97) ~[killbill-queue-0.2.15.jar:na]
at com.google.common.eventbus.EventBusThatThrowsException.dispatchQueuedEventsWithException(EventBusThatThrowsException.java:87) ~[killbill-queue-0.2.15.jar:na]
at com.google.common.eventbus.EventBusThatThrowsException.postWithException(EventBusThatThrowsException.java:71) ~[killbill-queue-0.2.15.jar:na]
at org.killbill.bus.DefaultPersistentBus.doProcessEvents(DefaultPersistentBus.java:106) ~[killbill-queue-0.2.15.jar:na]
at org.killbill.queue.DefaultQueueLifecycle$1.run(DefaultQueueLifecycle.java:107) [killbill-queue-0.2.15.jar:na]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_55]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_55]
at java.lang.Thread.run(Thread.java:745) [na:1.7.0_55]

I've searched a bit and found two very similar issues.

Would the solution simply be to have a SecurityApi.logout() method as well?

Thanks,
Val

Pierre-Alexandre Meyer

unread,
Jun 5, 2014, 8:49:00 AM6/5/14
to Valentin Crettaz, killbill...@googlegroups.com
On Wed, Jun 4, 2014 at 11:21 PM, Valentin Crettaz <valentin...@consulthys.com> wrote:
I've let my KB server run for a while (read overnight) and as I'm resuming testing I'm getting a shiro stacktrace complaining about inactive session.

The stack trace looks like this:
org.apache.shiro.session.UnknownSessionException: There is no session with id [8930]

For information, you can configure the session timeout via the org.killbill.rbac.globalSessionTimeout system property (defaults to 1h).

Would the solution simply be to have a SecurityApi.logout() method as well?

When do you login()? You should be able to do it on each API call without much impact.

Regardless, having a logout() method for symmetry would be good. Also, exposing the Subject capabilities would be useful as well. I've created https://github.com/killbill/killbill/issues/202 for these.

Let me know if forcing a login each time solves your issue.

--
Pierre

Valentin Crettaz

unread,
Jun 5, 2014, 9:14:03 AM6/5/14
to killbill...@googlegroups.com, valentin...@consulthys.com

I've let my KB server run for a while (read overnight) and as I'm resuming testing I'm getting a shiro stacktrace complaining about inactive session.

The stack trace looks like this:
org.apache.shiro.session.UnknownSessionException: There is no session with id [8930]

For information, you can configure the session timeout via the org.killbill.rbac.globalSessionTimeout system property (defaults to 1h).

I can try that, but I doubt it will help as the issue is not to keep long-running sessions around.
 
Would the solution simply be to have a SecurityApi.logout() method as well?

When do you login()? You should be able to do it on each API call without much impact.

Regardless, having a logout() method for symmetry would be good. Also, exposing the Subject capabilities would be useful as well. I've created https://github.com/killbill/killbill/issues/202 for these.

Let me know if forcing a login each time solves your issue.

I do a login before each call. The issue seems to be that since there's no logout, the session will expire after the default timeout (1h), but somehow the session "sticks around" with the subject and on the next login call the session has expired and is not there anymore, hence the stack trace.
I think if we can login/doStuff/logout, it'd get rid of this issue altogether.

Val

Pierre-Alexandre Meyer

unread,
Jun 5, 2014, 9:39:18 AM6/5/14
to Valentin Crettaz, killbill...@googlegroups.com
On Thu, Jun 5, 2014 at 9:14 AM, Valentin Crettaz <valentin...@consulthys.com> wrote:
For information, you can configure the session timeout via the org.killbill.rbac.globalSessionTimeout system property (defaults to 1h).

I can try that, but I doubt it will help as the issue is not to keep long-running sessions around.

Of course, I just wanted to point out the configuration option.
 
I do a login before each call. The issue seems to be that since there's no logout, the session will expire after the default timeout (1h), but somehow the session "sticks around" with the subject and on the next login call the session has expired and is not there anymore, hence the stack trace.

Ah, yes, you're right. Kaui actually does the same (logout/login): https://github.com/killbill/killbill-admin-ui/commit/df06f171030dc77405c0dd352237d93d10f3bac5
 
I think if we can login/doStuff/logout, it'd get rid of this issue altogether.

osgi-shiro branches updated (note: untested).

Note that subject capabilities was actually already done via getCurrentUserPermissions(), which can be used to check if your session is still valid.

--
Pierre

Valentin Crettaz

unread,
Jun 5, 2014, 10:22:45 AM6/5/14
to killbill...@googlegroups.com, valentin...@consulthys.com
Awesome thank you. I'm going to try that out.

Val
Reply all
Reply to author
Forward
0 new messages