prepaid usage billing

607 views
Skip to first unread message

mich...@wush.net

unread,
Mar 12, 2015, 3:01:17 PM3/12/15
to killbill...@googlegroups.com
We are looking to implement a system in which users can load a balance onto their account, spend it down via metered use, and then top off their balance when it is low. I believe in previous threads this model has been referred to as consumable in advance.

We have consumable in arrear workflow working using the features built in to killbill. Has there been any work done on the prepaid model?


Currently our thought for implementation is:
a. When a user wants to top off their account, initiate a payment using the direct payment API. If successful, post a credit to the account in the same amount (POST /credits).
b. Turn off account auto-invoicing and auto-billing. At the start of each month, create a new invoice for the user using (POST /invoices). As usage occurs, create an external charge for each item (POST /invoices/charges). At the end of the month, trigger a payment for any remaining balance if there is one.
c. Our understanding is that account CBA is the current credit on the account minus any outstanding invoices, so with the two above in place, we should be able to monitor CBA for current balance while the invoice is still open. If below a threshold, we can initiate a new payment. If it hits zero, we can suspend the account until this is fixed.


We had the following questions:
1. Can you offer any input on our overall approach?
2. Is there any api call that both processes the payment and increases the credit? i.e. similar to what payAllInvoices does for external payments in excess of existing invoices, but actually triggering the payment as well.
3. Even with AUTO_PAY_OFF, if there is credit on the account, it will be applied as soon as line items are added to an invoice. Is it possible to delay this until we actually trigger a payment on the invoice?
4. Is there a way to close an invoice to future changes? Once the invoice is closed out, we want to prevent any further external charges from being applied to it.
5. Another problem we have run into is small invoices. For consumable in appear, our users can sometimes end up with a bill under $0.50 on a given month. Stripe will not process such a payment, so the invoice payment fails. Even when a number of these invoices accrue, the payAllInvoices function tries to pay each individually rather than doing one payment for the full outstanding balance so they continue to fail. Is there a way around this issue?


Thank you for any guidance you can offer.

stephane brossier

unread,
Mar 13, 2015, 2:41:31 PM3/13/15
to Michael Ching, killbill...@googlegroups.com
Michael,


On Thu, Mar 12, 2015 at 12:01 PM, <mich...@wush.net> wrote:
We are looking to implement a system in which users can load a balance onto their account, spend it down via metered use, and then top off their balance when it is low.  I believe in previous threads this model has been referred to as consumable in advance.

We have consumable in arrear workflow working using the features built in to killbill.  Has there been any work done on the prepaid model?


Currently our thought for implementation is:
a.  When a user wants to top off their account, initiate a payment using the direct payment API.  If successful, post a credit to the account in the same amount (POST /credits).
b.  Turn off account auto-invoicing and auto-billing.  At the start of each month, create a new invoice for the user using (POST /invoices).  As usage occurs, create an external charge for each item (POST /invoices/charges).  At the end of the month, trigger a payment for any remaining balance if there is one.
c.  Our understanding is that account CBA is the current credit on the account minus any outstanding invoices, so with the two above in place, we should be able to monitor CBA for current balance while the invoice is still open.  If below a threshold, we can initiate a new payment.  If it hits zero, we can suspend the account until this is fixed.


We had the following questions:
1.  Can you offer any input on our overall approach?


At a high level, what you are proposing works, but you are basically taking all the logic outside Kill Bill  because we did not implement yet the 'consumable in advance' model.  The problem is that this will be hard to maintain on your side, and also this will probably end up being a bit clunky. For instance, all the recurring logic we implemented in KB is based on the BCD (BillCycleDay), and is linked to the various alignments, takes into consideration months with different length, leap year, ... and redoing all that outside is somehow risky. This is a clever approach though ;-)

If you have some engineering resources, maybe we could work together and help you implement the missing pieces. 

2.  Is there any api call that both processes the payment and increases the credit?  i.e. similar to what payAllInvoices does for external payments in excess of existing invoices, but actually triggering the payment as well.

We don't offer such 'combo' calls, you will have to make several requests. If you don't need atomicity in these two calls that should not be such a problem.

 
3.  Even with AUTO_PAY_OFF, if there is credit on the account, it will be applied as soon as line items are added to an invoice.  Is it possible to delay this until we actually trigger a payment on the invoice?

No, today,  the CBA calculation is embedded into the invoicing logic and always consume the credit when this is available. I think coming with a general setting that would prevent that should not be too hard to add, but you probably need that logic on a finer granularity anyway? Also, the issue of that adding knobs into the system makes the testing matrix much more complex. 


4.  Is there a way to close an invoice to future changes?  Once the invoice is closed out, we want to prevent any further external charges from being applied to it.

Nope we don't support it today, which means you would have to 'protect' that invoice from the client side.
 
5.  Another problem we have run into is small invoices.  For consumable in appear, our users can sometimes end up with a bill under $0.50 on a given month.  Stripe will not process such a payment, so the invoice payment fails.  Even when a number of these invoices accrue, the payAllInvoices function tries to pay each individually rather than doing one payment for the full outstanding balance so they continue to fail.  Is there a way around this issue?


Ah, that one is tricky indeed. There are multiple topics here:
1. Providing a way to not make a payment on small (TBD) invoices
2. Allow to later have one payment span multiple invoices

For 1, we have a payment routing api, which basically intercepts the payment call and let you add you custom logic; you could add a plugin implementing that api that would behave like that:
- If payment is small enough, abort payment
- If payment is big enough, look for existing small unpaid invoices, and for each invoice, add a 'credit' (which brings balance to 0, and at the same time add a 'charge' on the invoice for which payment is occurring, and continue the flow, which would end up aggregating your small unpaid invoices on the current larger invoice.

For 2, it would be desirable to implement that, but the story is slightly complex because of the issues with refund with item adjustments (which in that case would now span multiple invoices). The world of billing is surprisingly complicated ;-) Again, we can provide some help and guidance if you are interested to make some change in the code to support it.

Good luck!

Stéphane





Thank you for any guidance you can offer.

--
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/75fa6c15-1cdc-4546-946f-2b18b2e95c1c%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

wushs...@gmail.com

unread,
Mar 24, 2015, 5:39:38 PM3/24/15
to killbill...@googlegroups.com, mich...@wush.net
We are attempting to implement the prepaid logic mostly within an event plugin which listens on :INVOICE_ADJUSTMENT.

When we try to call @kb_apis.payment_api.create_purchase, we get an error. The context works fine for the other @kb_apis calls being done. Below is our code and the error we see. Please let us know if you see something we should be doing differently.

def on_event(event)
if event.event_type == :INVOICE_ADJUSTMENT
context = @kb_apis.create_context(event.tenant_id)
payment_methods = @kb_apis.payment_api.get_account_payment_methods(event.account_id, false, nil, context)
if payment_methods.count > 0
account = @kb_apis.account_user_api.get_account_by_id(event.account_id, context)
puts "#{account} #{payment_methods[0].id} #{context}"
auth = @kb_apis.payment_api.create_purchase(account, payment_methods[0].id, nil, '100.00', 'USD', nil, nil, nil, context)
end
end
end

2015-03-24 20:52:34,629 [Thread-18] WARN o.k.b.k.0.1.3 - Failure in on_event: This subject is anonymous - it does not have any identifying principals and authorization operations require an identity to check against. A Subject instance will acquire these identifying principals automatically after a successful login is performed be executing org.apache.shiro.subject.Subject.login(AuthenticationToken) or when 'Remember Me' functionality is enabled by the SecurityManager. This exception can also occur when a previously logged-in Subject has logged out which makes it anonymous again. Because an identity is currently not known due to any of these conditions, authorization is denied.
org.apache.shiro.subject.support.DelegatingSubject.assertAuthzCheckPossible(org/apache/shiro/subject/support/DelegatingSubject.java:199)
org.apache.shiro.subject.support.DelegatingSubject.checkPermission(org/apache/shiro/subject/support/DelegatingSubject.java:204)
org.killbill.billing.util.security.api.DefaultSecurityApi.checkCurrentUserPermissions(org/killbill/billing/util/security/api/DefaultSecurityApi.java:105)
org.killbill.billing.util.glue.KillbillApiAopModule$ProfilingMethodInterceptor$1.execute(org/killbill/billing/util/glue/KillbillApiAopModule.java:52)
org.killbill.commons.profiling.Profiling.executeWithProfiling(org/killbill/commons/profiling/Profiling.java:33)
org.killbill.billing.util.glue.KillbillApiAopModule$ProfilingMethodInterceptor.invoke(org/killbill/billing/util/glue/KillbillApiAopModule.java:49)
org.killbill.billing.util.security.PermissionAnnotationHandler.assertAuthorized(org/killbill/billing/util/security/PermissionAnnotationHandler.java:56)
org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor.assertAuthorized(org/apache/shiro/authz/aop/AuthorizingAnnotationMethodInterceptor.java:84)
org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor.invoke(org/apache/shiro/authz/aop/AuthorizingAnnotationMethodInterceptor.java:67)
org.killbill.billing.util.security.AopAllianceMethodInterceptorAdapter.invoke(org/killbill/billing/util/security/AopAllianceMethodInterceptorAdapter.java:32)
java.lang.reflect.Method.invoke(java/lang/reflect/Method.java:483)
RUBY.create_authorization(/home/killbill/bundles/plugins/ruby/prepaid-account-cba-plugin/0.01/gems/gems/killbill-3.2.2/lib/killbill/gen/api/payment_api.rb:82)
RUBY.on_event(/home/killbill/bundles/plugins/ruby/prepaid-account-cba-plugin/0.01/gems/gems/prepaid-account-cba-plugin-0.01/lib/prepaid.rb:40)
RUBY.onEvent(/home/killbill/bundles/plugins/ruby/prepaid-account-cba-plugin/0.01/gems/gems/killbill-3.2.2/lib/killbill/gen/plugin-api/notification_plugin_api.rb:48)
Killbill$$Plugin$$Api$$NotificationPluginApi_596338834.onEvent(Killbill$$Plugin$$Api$$NotificationPluginApi_596338834.gen:13)
Killbill$$Plugin$$Api$$NotificationPluginApi_596338834.onEvent(Killbill$$Plugin$$Api$$NotificationPluginApi_596338834.gen:13)
org.killbill.billing.osgi.bundles.jruby.JRubyNotificationPlugin$1.doCall(org/killbill/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java:43)
org.killbill.billing.osgi.bundles.jruby.JRubyNotificationPlugin$1.doCall(org/killbill/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java:40)
org.killbill.billing.osgi.bundles.jruby.JRubyPlugin.callWithRuntimeAndChecking(org/killbill/billing/osgi/bundles/jruby/JRubyPlugin.java:241)
org.killbill.billing.osgi.bundles.jruby.JRubyNotificationPlugin.handleKillbillEvent(org/killbill/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java:40)
org.killbill.killbill.osgi.libs.killbill.OSGIKillbillEventDispatcher$1$1.update(org/killbill/killbill/osgi/libs/killbill/OSGIKillbillEventDispatcher.java:68)
org.killbill.billing.osgi.KillbillEventObservable.notifyObservers(org/killbill/billing/osgi/KillbillEventObservable.java:70)
org.killbill.billing.osgi.KillbillEventObservable.handleKillbillEvent(org/killbill/billing/osgi/KillbillEventObservable.java:79)
java.lang.reflect.Method.invoke(java/lang/reflect/Method.java:483)
com.google.common.eventbus.EventHandler.handleEvent(com/google/common/eventbus/EventHandler.java:74)
com.google.common.eventbus.EventBusThatThrowsException.dispatchWithException(com/google/common/eventbus/EventBusThatThrowsException.java:97)
com.google.common.eventbus.EventBusThatThrowsException.dispatchQueuedEventsWithException(com/google/common/eventbus/EventBusThatThrowsException.java:87)
com.google.common.eventbus.EventBusThatThrowsException.postWithException(com/google/common/eventbus/EventBusThatThrowsException.java:71)
org.killbill.bus.DefaultPersistentBus.doProcessEvents(org/killbill/bus/DefaultPersistentBus.java:135)
org.killbill.queue.DefaultQueueLifecycle$1.run(org/killbill/queue/DefaultQueueLifecycle.java:111)
java.util.concurrent.ThreadPoolExecutor.runWorker(java/util/concurrent/ThreadPoolExecutor.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run(java/util/concurrent/ThreadPoolExecutor.java:617)
java.lang.Thread.run(java/lang/Thread.java:745)
2015-03-24 20:52:34,630 [Thread-18] WARN o.k.b.k.0.1.3 - Failure in on_event: This subject is anonymous - it does not have any identifying principals and authorization operations require an identity to check against. A Subject instance will acquire these identifying principals automatically after a successful login is performed be executing org.apache.shiro.subject.Subject.login(AuthenticationToken) or when 'Remember Me' functionality is enabled by the SecurityManager. This exception can also occur when a previously logged-in Subject has logged out which makes it anonymous again. Because an identity is currently not known due to any of these conditions, authorization is denied.
org.apache.shiro.subject.support.DelegatingSubject.assertAuthzCheckPossible(org/apache/shiro/subject/support/DelegatingSubject.java:199)
org.apache.shiro.subject.support.DelegatingSubject.checkPermission(org/apache/shiro/subject/support/DelegatingSubject.java:204)
org.killbill.billing.util.security.api.DefaultSecurityApi.checkCurrentUserPermissions(org/killbill/billing/util/security/api/DefaultSecurityApi.java:105)
org.killbill.billing.util.glue.KillbillApiAopModule$ProfilingMethodInterceptor$1.execute(org/killbill/billing/util/glue/KillbillApiAopModule.java:52)
org.killbill.commons.profiling.Profiling.executeWithProfiling(org/killbill/commons/profiling/Profiling.java:33)
org.killbill.billing.util.glue.KillbillApiAopModule$ProfilingMethodInterceptor.invoke(org/killbill/billing/util/glue/KillbillApiAopModule.java:49)
org.killbill.billing.util.security.PermissionAnnotationHandler.assertAuthorized(org/killbill/billing/util/security/PermissionAnnotationHandler.java:56)
org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor.assertAuthorized(org/apache/shiro/authz/aop/AuthorizingAnnotationMethodInterceptor.java:84)
org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor.invoke(org/apache/shiro/authz/aop/AuthorizingAnnotationMethodInterceptor.java:67)
org.killbill.billing.util.security.AopAllianceMethodInterceptorAdapter.invoke(org/killbill/billing/util/security/AopAllianceMethodInterceptorAdapter.java:32)
java.lang.reflect.Method.invoke(java/lang/reflect/Method.java:483)
RUBY.create_authorization(/home/killbill/bundles/plugins/ruby/prepaid-account-cba-plugin/0.01/gems/gems/killbill-3.2.2/lib/killbill/gen/api/payment_api.rb:82)
RUBY.on_event(/home/killbill/bundles/plugins/ruby/prepaid-account-cba-plugin/0.01/gems/gems/prepaid-account-cba-plugin-0.01/lib/prepaid.rb:40)
RUBY.onEvent(/home/killbill/bundles/plugins/ruby/prepaid-account-cba-plugin/0.01/gems/gems/killbill-3.2.2/lib/killbill/gen/plugin-api/notification_plugin_api.rb:48)
Killbill$$Plugin$$Api$$NotificationPluginApi_596338834.onEvent(Killbill$$Plugin$$Api$$NotificationPluginApi_596338834.gen:13)
Killbill$$Plugin$$Api$$NotificationPluginApi_596338834.onEvent(Killbill$$Plugin$$Api$$NotificationPluginApi_596338834.gen:13)
org.killbill.billing.osgi.bundles.jruby.JRubyNotificationPlugin$1.doCall(org/killbill/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java:43)
org.killbill.billing.osgi.bundles.jruby.JRubyNotificationPlugin$1.doCall(org/killbill/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java:40)
org.killbill.billing.osgi.bundles.jruby.JRubyPlugin.callWithRuntimeAndChecking(org/killbill/billing/osgi/bundles/jruby/JRubyPlugin.java:241)
org.killbill.billing.osgi.bundles.jruby.JRubyNotificationPlugin.handleKillbillEvent(org/killbill/billing/osgi/bundles/jruby/JRubyNotificationPlugin.java:40)
org.killbill.killbill.osgi.libs.killbill.OSGIKillbillEventDispatcher$1$1.update(org/killbill/killbill/osgi/libs/killbill/OSGIKillbillEventDispatcher.java:68)
org.killbill.billing.osgi.KillbillEventObservable.notifyObservers(org/killbill/billing/osgi/KillbillEventObservable.java:70)
org.killbill.billing.osgi.KillbillEventObservable.handleKillbillEvent(org/killbill/billing/osgi/KillbillEventObservable.java:79)
java.lang.reflect.Method.invoke(java/lang/reflect/Method.java:483)
com.google.common.eventbus.EventHandler.handleEvent(com/google/common/eventbus/EventHandler.java:74)
com.google.common.eventbus.EventBusThatThrowsException.dispatchWithException(com/google/common/eventbus/EventBusThatThrowsException.java:97)
com.google.common.eventbus.EventBusThatThrowsException.dispatchQueuedEventsWithException(com/google/common/eventbus/EventBusThatThrowsException.java:87)
com.google.common.eventbus.EventBusThatThrowsException.postWithException(com/google/common/eventbus/EventBusThatThrowsException.java:71)
org.killbill.bus.DefaultPersistentBus.doProcessEvents(org/killbill/bus/DefaultPersistentBus.java:135)
org.killbill.queue.DefaultQueueLifecycle$1.run(org/killbill/queue/DefaultQueueLifecycle.java:111)
java.util.concurrent.ThreadPoolExecutor.runWorker(java/util/concurrent/ThreadPoolExecutor.java:1142)
java.util.concurrent.ThreadPoolExecutor$Worker.run(java/util/concurrent/ThreadPoolExecutor.java:617)
java.lang.Thread.run(java/lang/Thread.java:745)
2015-03-24 20:52:34,674 [bus_ext_events-th] ERROR o.killbill.bus.DefaultPersistentBus - Fatal Bus dispatch error, data corruption...
java.lang.IllegalStateException: Unexpected PaymentApiException for notification plugin
at org.killbill.billing.osgi.bundles.jruby.JRubyNotificationPlugin.handleKillbillEvent(JRubyNotificationPlugin.java:48) ~[na:na]
at org.killbill.killbill.osgi.libs.killbill.OSGIKillbillEventDispatcher$1$1.update(OSGIKillbillEventDispatcher.java:68) ~[killbill-platform-osgi-bundles-lib-killbill-0.1.3.jar:na]
at org.killbill.billing.osgi.KillbillEventObservable.notifyObservers(KillbillEventObservable.java:70) ~[killbill-platform-osgi-0.1.3.jar:na]
at org.killbill.billing.osgi.KillbillEventObservable.handleKillbillEvent(KillbillEventObservable.java:79) ~[killbill-platform-osgi-0.1.3.jar:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_25]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_25]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_25]
at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_25]
at com.google.common.eventbus.EventHandler.handleEvent(EventHandler.java:74) ~[guava-15.0.jar:na]
at com.google.common.eventbus.EventBusThatThrowsException.dispatchWithException(EventBusThatThrowsException.java:97) ~[killbill-queue-0.2.32.jar:na]
at com.google.common.eventbus.EventBusThatThrowsException.dispatchQueuedEventsWithException(EventBusThatThrowsException.java:87) ~[killbill-queue-0.2.32.jar:na]
at com.google.common.eventbus.EventBusThatThrowsException.postWithException(EventBusThatThrowsException.java:71) ~[killbill-queue-0.2.32.jar:na]
at org.killbill.bus.DefaultPersistentBus.doProcessEvents(DefaultPersistentBus.java:135) ~[killbill-queue-0.2.32.jar:na]
at org.killbill.queue.DefaultQueueLifecycle$1.run(DefaultQueueLifecycle.java:111) [killbill-queue-0.2.32.jar:na]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_25]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_25]
at java.lang.Thread.run(Thread.java:745) [na:1.8.0_25]
Caused by: org.killbill.billing.payment.plugin.api.PaymentPluginApiException: null
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_25]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_25]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_25]
at java.lang.reflect.Constructor.newInstance(Constructor.java:408) ~[na:1.8.0_25]
at org.jruby.javasupport.JavaConstructor.newInstanceDirect(JavaConstructor.java:291) ~[na:na]
at org.jruby.java.invokers.ConstructorInvoker.call(ConstructorInvoker.java:104) ~[na:na]
at org.jruby.java.invokers.ConstructorInvoker.call(ConstructorInvoker.java:197) ~[na:na]
at org.jruby.runtime.callsite.CachingCallSite.cacheAndCall(CachingCallSite.java:356) ~[na:na]
at org.jruby.runtime.callsite.CachingCallSite.callBlock(CachingCallSite.java:213) ~[na:na]
at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:217) ~[na:na]
at org.jruby.java.proxies.ConcreteJavaProxy$2.call(ConcreteJavaProxy.java:56) ~[na:na]
at org.jruby.runtime.callsite.CachingCallSite.callBlock(CachingCallSite.java:211) ~[na:na]
at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:217) ~[na:na]
at org.jruby.RubyClass.newInstance(RubyClass.java:858) ~[na:na]
at org.jruby.RubyClass$INVOKER$i$newInstance.call(RubyClass$INVOKER$i$newInstance.gen) ~[na:na]
at org.jruby.internal.runtime.methods.JavaMethod$JavaMethodZeroOrOneOrTwoOrNBlock.call(JavaMethod.java:314) ~[na:na]
at org.jruby.java.proxies.ConcreteJavaProxy$3.call(ConcreteJavaProxy.java:155) ~[na:na]
at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:202) ~[na:na]
at org.jruby.ast.CallTwoArgNode.interpret(CallTwoArgNode.java:59) ~[na:na]
at org.jruby.ast.FCallOneArgNode.interpret(FCallOneArgNode.java:36) ~[na:na]
at org.jruby.ast.NewlineNode.interpret(NewlineNode.java:105) ~[na:na]
at org.jruby.ast.BlockNode.interpret(BlockNode.java:71) ~[na:na]
at org.jruby.ast.BlockNode.interpret(BlockNode.java:71) ~[na:na]
at org.jruby.ast.RescueBodyNode.interpret(RescueBodyNode.java:108) ~[na:na]
at org.jruby.ast.RescueNode.handleJavaException(RescueNode.java:204) ~[na:na]
at org.jruby.ast.RescueNode.interpret(RescueNode.java:137) ~[na:na]
at org.jruby.ast.EnsureNode.interpret(EnsureNode.java:96) ~[na:na]
at org.jruby.ast.BeginNode.interpret(BeginNode.java:83) ~[na:na]
at org.jruby.ast.NewlineNode.interpret(NewlineNode.java:105) ~[na:na]
at org.jruby.ast.BlockNode.interpret(BlockNode.java:71) ~[na:na]
at org.jruby.evaluator.ASTInterpreter.INTERPRET_METHOD(ASTInterpreter.java:74) ~[na:na]
at org.jruby.internal.runtime.methods.InterpretedMethod.call(InterpretedMethod.java:112) ~[na:na]
at org.jruby.internal.runtime.methods.DefaultMethod.call(DefaultMethod.java:164) ~[na:na]
at Killbill$$Plugin$$Api$$NotificationPluginApi_596338834.onEvent(Killbill$$Plugin$$Api$$NotificationPluginApi_596338834.gen:13) ~[na:na]
at org.killbill.billing.osgi.bundles.jruby.JRubyNotificationPlugin$1.doCall(JRubyNotificationPlugin.java:43) ~[na:na]
at org.killbill.billing.osgi.bundles.jruby.JRubyNotificationPlugin$1.doCall(JRubyNotificationPlugin.java:40) ~[na:na]
at org.killbill.billing.osgi.bundles.jruby.JRubyPlugin.callWithRuntimeAndChecking(JRubyPlugin.java:241) ~[na:na]
at org.killbill.billing.osgi.bundles.jruby.JRubyNotificationPlugin.handleKillbillEvent(JRubyNotificationPlugin.java:40) ~[na:na]
... 16 common frames omitted

Pierre-Alexandre Meyer

unread,
Mar 25, 2015, 9:57:45 AM3/25/15
to wushs...@gmail.com, killbill...@googlegroups.com, Michael Ching
On Tue, Mar 24, 2015 at 5:39 PM, <wushs...@gmail.com> wrote:
When we try to call @kb_apis.payment_api.create_purchase, we get an error.  The context works fine for the other @kb_apis calls being done. 

It looks like the API call fails because the request isn't authenticated (hence permissions cannot be verified -- to trigger a payment, the caller needs to have the permission PAYMENT_CAN_TRIGGER_PAYMENT).
 
Could you try to log-in explicitly before the call? Something like @kb_apis.security_api.login('admin', 'password') ?

--
Pierre

mich...@wush.net

unread,
Mar 25, 2015, 12:54:58 PM3/25/15
to killbill...@googlegroups.com, wushs...@gmail.com, mich...@wush.net
> Could you try to log-in explicitly before the call? Something like
> @kb_apis.security_api.login('admin', 'password') ?

Thanks, I get this error when trying the call:

2015-03-25 16:39:16,849 [Thread-18] WARN o.k.b.k.0.1.4 - Failure in on_event: undefined method `security_api' for #<Killbill::Plugin::KillbillApi:0x17dc65e2>
/home/killbill/bundles/plugins/ruby/prepaid-account-cba-plugin/0.01/gems/gems/killbill-3.2.2/lib/killbill/killbill_api.rb:24:in `method_missing'
/home/killbill/bundles/plugins/ruby/prepaid-account-cba-plugin/0.01/gems/gems/prepaid-account-cba-plugin-0.01/lib/prepaid.rb:42:in `on_event'
/home/killbill/bundles/plugins/ruby/prepaid-account-cba-plugin/0.01/gems/gems/killbill-3.2.2/lib/killbill/gen/plugin-api/notification_plugin_api.rb:48:in `onEvent'
Killbill$$Plugin$$Api$$NotificationPluginApi_732891356.gen:13:in `onEvent'

One thing I noticed when searching for the login call is that most of the api methods are wrapped in https://github.com/killbill/killbill-plugin-framework-ruby/tree/master/lib/killbill/gen/api but security_api does not have such a file.

Pierre-Alexandre Meyer

unread,
Mar 25, 2015, 7:54:27 PM3/25/15
to Michael Ching, killbill...@googlegroups.com, wushs...@gmail.com
On Wed, Mar 25, 2015 at 12:54 PM, <mich...@wush.net> wrote:
One thing I noticed when searching for the login call is that most of the api methods are wrapped in https://github.com/killbill/killbill-plugin-framework-ruby/tree/master/lib/killbill/gen/api but security_api does not have such a file.

Aww, you are right - it looks like it is not exported yet :( I've created https://github.com/killbill/killbill-plugin-framework-ruby/issues/35 for tracking.

Until this is fixed, you have several possibilities as work-arounds. Either use the ruby http client for the purchase call from your plugin (calling back on 127.0.0.1) or implement your logic outside of Kill Bill (by listening to push notifications).

Alternatively, you could also take a stab at fixing it :-) The OSGI layer is a bit tricky to navigate but if you want to take a stab at it, feel free to reach out for help.

--
Pierre

mich...@wush.net

unread,
Mar 26, 2015, 2:08:41 PM3/26/15
to killbill...@googlegroups.com, mich...@wush.net, wushs...@gmail.com
> Aww, you are right - it looks like it is not exported yet :( I've created https://github.com/killbill/killbill-plugin-framework-ruby/issues/35 for tracking.
>
> Until this is fixed, you have several possibilities as work-arounds. Either use the ruby http client for the purchase call from your plugin (calling back on 127.0.0.1) or implement your logic outside of Kill Bill (by listening to push notifications).

Thank you, for now we have went ahead and used the ruby client within the plugin. The payment, credit, and adjustment calls are all working for us.

Atomicity would be nice, but I imagine assume this cannot be accomplished using the plugin system.

Pierre-Alexandre Meyer

unread,
Mar 26, 2015, 4:23:54 PM3/26/15
to Michael Ching, killbill...@googlegroups.com, wushs...@gmail.com
On Thu, Mar 26, 2015 at 2:08 PM, <mich...@wush.net> wrote:
Thank you, for now we have went ahead and used the ruby client within the plugin.  The payment, credit, and adjustment calls are all working for us.

Great!

Atomicity would be nice, but I imagine assume this cannot be accomplished using the plugin system.

No, and it is by design, so that plugins do not impact the main system (we treat plugins as third-party code, potentially buggy, insecure, etc.).

--
Pierre
Reply all
Reply to author
Forward
0 new messages