theopencloudengine / killbill Goto Github PK
View Code? Open in Web Editor NEWThis project forked from killbill/killbill
Open-Source Subscription Billing & Payment Platform
Home Page: http://killbill.io/
This project forked from killbill/killbill
Open-Source Subscription Billing & Payment Platform
Home Page: http://killbill.io/
11월 작업까지
12월 작업까지
설계 및 연계 관련, 각 WBS 항목별 산출물 문서 작성
100 메가 이상된 사용량에 대하여 월별로 어떻게 계산
톰캣의 Jvm 프로퍼티에 다음의 값이 설정되어있으면 런타임시 동적카달로그 배포하도록 함.
-Dorg.killbill.catalog.mode=dynamic
Maven 빌드 실행시에는 test 용 defaultCatalog 배포로 정상적으로 빌드됨.
페이팔 플러그인의 ActiveMerchant api 호출시 레퍼런스 트랜잭션을 실행시켜야 할 때 paypal deveope 에 명시된 스펙과 다른 스펙을 호출하면서 결제가 되지 않고 있다.
Runtime ruby 코드 디버깅 => ruby console
우분투 환경에서 KPM으로 설치시, 서버가 초반 몇번의 트랜잭션만 받고 행이 걸림.
이후 간간히 행이 풀렸다 걸렸다 하다 완전 무응답 상태로 돌입하고, 의미있는 로그가 남지 않음.
KPM 설치된 모듈의 디버깅 방법을 알기.
Next. Invalid Time Zone Reject during paypal billing agreement checkout. why? => UTC 이외에는 되지가 않고있음.
중요
bus_events 에 쌓이는 레코드 처리 속도가, api 요청 건을 따라잡지 못하는경우,
부하가 누적되다가 자폭함.
이후 api 요청을 중단했을경우, 남은 bus_events 가 처리되지는 않고 계속 쿼리만 날려 mysql 도 부하를 주다가, mysql 도 자폭시킴.
Was Xmx 2G 기준으로, 10쓰레드 서브스크립션 벌크 생성중에는 bus_events 레코드가 3개 이하로 안정적이다가,
20쓰레드 벌크 생성중에 bus_events 가 따라잡지 못하더니 레코드가 30개 이상 쌓였을때 자폭하였음.
자폭하는건 이해가 되는데 왜 놓쳐버린 bus_events 를 재실행을 하지 않는지는 예감이 그냥 소스코드를 대형 마켓플레이스 플랫폼용으로 설계를 하지 않아서, 부하 상황에서의 recovery 로직이 매우 허접하다는 느낌이 든다.
mysql 에서 다음의 에러가 남으로...
java.sql.SQLSyntaxErrorException: Row size too large (> 8126). Changing some columns to TEXT or BLOB may help
mysql 디렉토리의 my.cnf 파일을 다음으로 수정한다.
[mysqld]
.
.
max_allowed_packet=500M
innodb_file_per_table
innodb_file_format = Barracuda
innodb_log_file_size = 256M
이후 mysql 의 tenant_kv 테이블을 업데이트한다.
ALTER TABLE tenant_kvs ENGINE=InnoDB ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8;
이후 http://localhost:8080/1.0/kb/catalog 엔드포인트로 POST 전송을 한다.
전문을 보낼때의 헤더규칙.
String auth = this.user + ":" + this.password;
BASE64Encoder encoder = new BASE64Encoder();
String encode = encoder.encode(auth.getBytes());
Map mergeHeaders = new HashMap();
mergeHeaders.put("Authorization", "Basic " + encode);
mergeHeaders.put("Content-Type", "application/xml");
mergeHeaders.put("Accept", "application/json");
mergeHeaders.put("X-Killbill-CreatedBy", "OpenBill");
mergeHeaders.put("X-Killbill-ApiKey", this.apiKey);
mergeHeaders.put("X-Killbill-ApiSecret", this.apiSecret);
대용량 카달로그 업데이트시 소요시간
상품 수 | 상품 당 플랜 수 | 총 플랜 수 | 용량(KB) | 카달로그 생성 소요 시간(s) | 카달로그 전송과 서버처리 시간(s) | WAS 환경 |
---|---|---|---|---|---|---|
10000 | 1 | 10000 | 5734 | 4 | 12 | Xmx 2G |
20000 | 1 | 20000 | 10686 | 9 | 34 | Xmx 2G |
30000 | 1 | 30000 | 15637 | 18 | 51 | Xmx 2G |
40000 | 1 | 40000 | 20588 | 42 | 172 | Xmx 2G |
50000 | 1 | 50000 | 25539 | 79 | 369 | Xmx 2G |
150000 | 1 | 150000 | 86714 | 298 | 1182 | Xmx 2G |
org.osgi.framework.BundleException: Activator start error in bundle org.kill-bill.billing.killbill-platform-osgi-bundles-jruby-1 [21].
at org.apache.felix.framework.Felix.activateBundle(Felix.java:2204)
at org.apache.felix.framework.Felix.startBundle(Felix.java:2072)
at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:976)
at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:963)
at org.killbill.billing.osgi.FileInstall.startBundle(FileInstall.java:259)
at org.killbill.billing.osgi.BundleRegistry.startBundles(BundleRegistry.java:95)
at org.killbill.billing.osgi.DefaultOSGIService.start(DefaultOSGIService.java:103)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.killbill.billing.lifecycle.DefaultLifecycle.doFireStage(DefaultLifecycle.java:150)
at org.killbill.billing.lifecycle.DefaultLifecycle.fireSequence(DefaultLifecycle.java:137)
at org.killbill.billing.lifecycle.DefaultLifecycle.fireStartupSequencePriorEventRegistration(DefaultLifecycle.java:78)
at org.killbill.billing.server.listeners.KillbillPlatformGuiceListener.startLifecycle(KillbillPlatformGuiceListener.java:259)
at org.killbill.billing.server.listeners.KillbillPlatformGuiceListener.contextInitialized(KillbillPlatformGuiceListener.java:112)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5003)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5517)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652)
at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1839)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:618)
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:565)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1487)
at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:97)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1328)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1420)
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:848)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
at sun.rmi.transport.Transport$2.run(Transport.java:202)
at sun.rmi.transport.Transport$2.run(Transport.java:199)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:198)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:567)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.access$400(TCPTransport.java:619)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$1.run(TCPTransport.java:684)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$1.run(TCPTransport.java:681)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:681)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.jruby.exceptions.RaiseException: (LoadError) Could not load 'active_record/connection_adapters/jdbcmysql_adapter'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql', 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.
2017-01-05T03:25:10,348+0000 lvl='INFO', log='FileInstall', th='RMI TCP Connection(2)-127.0.0.1', xff='', rId='', aRId='', tRId='', Starting bundle jruby-killbill-kpm
데일리마다 한번씩 대용량 카달로그가 업데이트 된다는 가정하에, 카달로그 버젼을 업데이트 해가며 서브스크립션 차지 계산 및 인보이스 생성 능력을 테스트함.
카달로그 버젼 수 | 전체 플랜 수 | 전체 서브스크립션 수 | 인보이스 당 서브스크립션 수 | 인보이스 처리 속도(분당) | 모니터링 시간(분) |
---|---|---|---|---|---|
2 | 20000 | 10000 | 2 | 201 | 5 |
4 | 20000 | 10000 | 2 | 212 | 5 |
6 | 20000 | 10000 | 2 | 서버 정지 | 5 |
카달로그가 6개째가 되면서 2G 를 가지고 있는 was 가 힙메모리 부족이 뜨면서 죽음.
에러 원인은 서브스크립션 계산을 위해 카달로그 캐쉬를 사용하는 것과는 별개로, 예전 버전의 가격변동 여부를 확인하기 위해 데이터베이스에서 카달로그를 가져오는 과정에 힙메모리가 걸림.
결국 우려했던대로 DB 에서 카달로그를 다 가져오는 과정때문에 서버가 아무것도 못하고 빌빌대다 죽어버림...
Caused by: java.lang.OutOfMemoryError: Java heap space
at java.lang.StringCoding$StringDecoder.decode(StringCoding.java:149)
at java.lang.StringCoding.decode(StringCoding.java:193)
at java.lang.String.<init>(String.java:426)
at java.lang.String.<init>(String.java:491)
at org.mariadb.jdbc.internal.common.AbstractValueObject.getString(AbstractValueObject.java:82)
at org.mariadb.jdbc.internal.mysql.MySQLValueObject.getString(MySQLValueObject.java:78)
at org.mariadb.jdbc.MySQLResultSet.getString(MySQLResultSet.java:131)
at com.zaxxer.hikari.proxy.ResultSetJavassistProxy.getString(ResultSetJavassistProxy.java)
at org.killbill.commons.jdbi.mapper.LowerToCamelBeanMapper.map(LowerToCamelBeanMapper.java:126)
at org.skife.jdbi.v2.RegisteredMapper.map(RegisteredMapper.java:37)
at org.skife.jdbi.v2.Query$4.munge(Query.java:183)
at org.skife.jdbi.v2.QueryResultSetMunger.munge(QueryResultSetMunger.java:43)
at org.skife.jdbi.v2.SQLStatement.internalExecute(SQLStatement.java:1340)
at org.skife.jdbi.v2.Query.fold(Query.java:173)
at org.skife.jdbi.v2.Query.list(Query.java:82)
at org.skife.jdbi.v2.sqlobject.ResultReturnThing$IterableReturningThing.result(ResultReturnThing.java:255)
at org.skife.jdbi.v2.sqlobject.ResultReturnThing.map(ResultReturnThing.java:48)
at org.skife.jdbi.v2.sqlobject.QueryHandler.invoke(QueryHandler.java:45)
at org.skife.jdbi.v2.sqlobject.SqlObject.invoke(SqlObject.java:175)
at org.skife.jdbi.v2.sqlobject.SqlObject$2.intercept(SqlObject.java:92)
at org.skife.jdbi.v2.sqlobject.CloseInternalDoNotUseThisClass$$EnhancerByCGLIB$$ad218284.getTenantValueForKey(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperInvocationHandler$2.execute(EntitySqlDaoWrapperInvocationHandler.java:207)
at org.killbill.commons.profiling.Profiling.executeWithProfiling(Profiling.java:33)
at org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperInvocationHandler.invokeRaw(EntitySqlDaoWrapperInvocationHandler.java:204)
at org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperInvocationHandler.invokeSafely(EntitySqlDaoWrapperInvocationHandler.java:199)
at org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperInvocationHandler.access$000(EntitySqlDaoWrapperInvocationHandler.java:82)
at org.killbill.billing.util.entity.dao.EntitySqlDaoWrapperInvocationHandler$1.execute(EntitySqlDaoWrapperInvocationHandler.java:120)
at org.killbill.commons.profiling.Profiling.executeWithProfiling(Profiling.java:33)
사용자 생성 api 호출을 다음과 같이 돌렸을 때 데이트 유실율.
환경은 모두 local. 쓰레드가 100일때 데이터 유실 위험 높음.
쓰레드 | 반복횟수 | 총 횟수 | 완료된 데이터 건 | 소요시간(s) | 호출당 소요시간(ms) | 실패 사유 | WAS 환경 |
---|---|---|---|---|---|---|---|
1 | 10000 | 10000 | 10000(100%) | 4121 | 412.1 | Xmx 2G | |
10 | 1000 | 10000 | 10000(100%) | 943 | 94.3 | Xmx 2G | |
20 | 500 | 10000 | 10000(100%) | 607 | 60.7 | Xmx 2G | |
50 | 200 | 10000 | 10000(100%) | 524 | 52.4 | Xmx 2G | |
100 | 100 | 10000 | 9941(99.4%) | 498 | 49.8 | DB 인서트 실패 | Xmx 2G |
테스트 코드
public static void main(String[] args) throws Exception {
LogOff.logOff();
//포시에스 테넌트에 유저(구매자) 1만명 등록
//100명씩 100개 쓰레드로 접속.
int userNumbers = 100;
int thredsNumbers = 100;
for (int i = 0; i < thredsNumbers; i++) {
int startCount = i * userNumbers + 1;
int endCount = startCount + userNumbers;
CreateAcount test = new CreateAcount(startCount, endCount);
test.start();
}
}
public static class CreateAcount extends Thread {
int startCount;
int endCount;
public CreateAcount(int startCount, int endCount) {
this.startCount = startCount;
this.endCount = endCount;
}
public void run() {
String user = "admin";
String password = "password";
String apiKey = "forcs";
String apiSecret = "forcs";
String host = "localhost";
int port = 8080;
KillbillApi api = new KillbillApi(host, port, user, password, apiKey, apiSecret);
for (int c = startCount; c < endCount; c++) {
String name = "test-user-" + c;
String email = name + "@gmail.com";
String currency = "USD";
String externalKey = name;
int billCycleDayLocal = 5;
api.createAccount(name, email, currency, billCycleDayLocal, externalKey);
}
}
}
Multi currency
package org.uengine.garuda.test;
import org.joda.time.DateTime;
import org.killbill.billing.catalog.*;
import org.killbill.billing.catalog.api.*;
import org.killbill.billing.catalog.api.Currency;
import org.killbill.xmlloader.ValidatingConfig;
import org.killbill.xmlloader.XMLLoader;
import org.killbill.xmlloader.XMLSchemaGenerator;
import org.uengine.garuda.bill.kbapi.KillbillApi;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.util.*;
/**
* Created by uengine on 2016. 12. 13..
*/
public class AccountGenerateCase1 {
public static Marshaller marshaller(final Class<?> clazz) throws JAXBException, SAXException, IOException, TransformerException {
final JAXBContext context = JAXBContext.newInstance(clazz);
final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
final Marshaller um = context.createMarshaller();
final Schema schema = factory.newSchema(new StreamSource(XMLSchemaGenerator.xmlSchema(clazz)));
um.setSchema(schema);
return um;
}
/**
* 한화 1만원, 달러 10달러 프라이스 리스트
* @return
*/
public static DefaultInternationalPrice internationalPrice() {
DefaultInternationalPrice internationalPrice = new DefaultInternationalPrice();
DefaultPrice price1 = new DefaultPrice();
price1.setCurrency(Currency.USD);
price1.setValue(new BigDecimal(10));
DefaultPrice price2 = new DefaultPrice();
price2.setCurrency(Currency.KRW);
price2.setValue(new BigDecimal(10000));
DefaultPrice[] prices = new DefaultPrice[]{price1, price2};
internationalPrice.setPrices(prices);
return internationalPrice;
};
public static void main(String[] args) throws Exception {
String user = "admin";
String password = "password";
String apiKey = "bob";
String apiSecret = "lazar";
String host = "localhost";
int port = 8080;
//포시에스 테넌트 생성
KillbillApi api = new KillbillApi(host, port, user, password, apiKey, apiSecret);
api.createTenant("forcs-main");
Map tenant = api.getTenantByApiKey("forcs-main");
//포시에스 테넌트에 카달로그 생성(기본 상품 4개 보유 + 서드파티 상품 1만개 등록)
XMLLoader xmlLoader = new XMLLoader();
StandaloneCatalog catalog = xmlLoader.getObjectFromUri(new URI("catalog/FORCS.xml"), StandaloneCatalog.class);
catalog.getCatalogName();
Collection<Product> currentProducts = catalog.getCurrentProducts();
Collection<Plan> currentPlans = catalog.getCurrentPlans();
List<Product> currentProductsList = new ArrayList<Product>(currentProducts);
List<Plan> currentPlansList = new ArrayList<Plan>(currentPlans);
DefaultPriceListSet priceLists = catalog.getPriceLists();
DefaultPriceList defaultPricelist = priceLists.getDefaultPricelist();
Collection<Plan> plans = defaultPricelist.getPlans();
List<Plan> plansList = new ArrayList<Plan>(plans);
String subProductName = "ADD_ON_";
int subProductCount = 10000;
for (int i = 0; i < subProductCount; i++) {
//신규 프로덕트 이름
String productName = subProductName + i;
//신규 프로덕트
Product product = new DefaultProduct(productName, ProductCategory.STANDALONE);
//신규 플랜
String planName = subProductName + i + "_PLAN";
//finalPhase 를 지정한다.
DefaultPlanPhase finalPhase = new DefaultPlanPhase();
//finalPhase 의 phaseType
finalPhase.setPhaseType(PhaseType.EVERGREEN);
//finalPhase 의 Duration
finalPhase.setDuration(new DefaultDuration().setUnit(TimeUnit.UNLIMITED));
//finalPhase 의 recurring
DefaultRecurring recurring = new DefaultRecurring();
recurring.setBillingPeriod(BillingPeriod.MONTHLY);
recurring.setRecurringPrice(internationalPrice());
finalPhase.setRecurring(recurring);
//플랜 생성
DefaultPlan defaultPlan = new DefaultPlan();
defaultPlan.setProduct(product);
defaultPlan.setFinalPhase(finalPhase);
defaultPlan.setName(planName);
//생성한 프로덕트, 플랜, 디폴트 프라이스 리스트 등록
currentProductsList.add(product);
currentPlansList.add(defaultPlan);
plansList.add(new DefaultPlan().setName(planName));
}
//카탈로그에 프로덕트 리스트, 플랜리스트, 프라이스 리스트 오버라이드
currentProducts = new HashSet<Product>(currentProductsList);
currentPlans = new HashSet<Plan>(currentPlansList);
defaultPricelist.setPlans(new HashSet<Plan>(plansList));
catalog.setProducts(currentProducts);
catalog.setPlans(currentPlans);
//xml 변환
Marshaller m = marshaller(StandaloneCatalog.class);
File file = new File("/Users/uengine/IdeaProjects/OpenBill/openbill-test/src/main/resources/catalog/override.xml");
m.marshal(catalog,file);
//포시에스 테넌트에 유저(구매자) 100만명 등록
}
}
인보이스 아이템 생성시, 유엔진 빌링에 저장되어 있는 플랜 display_name 을 description 에 사용할 수 있도록 한다.
다음의 릴리즈들을 적용하였음.
https://github.com/killbill/killbill/releases/tag/killbill-0.18.3
https://github.com/killbill/killbill/releases/tag/killbill-0.18.4
https://github.com/killbill/killbill/releases/tag/killbill-0.18.5
https://github.com/killbill/killbill/releases/tag/killbill-0.18.6
0.18.7 은 Nightly build version
Addon / Standalone (product type) 처리
invoice 등에 출력되는 텍스트의 다국어 처리 (i.e. adjustment...)
이메일 트랜스레이션 파일 처리과정중 utf-8 인풋스트림 적용
소스코드 : here
ㄱ. kaui 에서 인스톨 과정
ㄴ. 또는 커스텀 빌드
프로덕션 모드 세팅
/var/tmp/bundles/plugins/ruby/killbill-paypal-express/5.0.0/ROOT 폴더에 paypal_express.yml 파일이 있다.
:paypal_express:
:test: false
:paypal_express:
:signature: <%= ENV['SIGNATURE'] %>
:login: <%= ENV['LOGIN'] %>
:password: <%= ENV['PASSWORD'] %>
:test: true
:database:
# SQLite (development)
:adapter: sqlite3
:database: test.db
# For MySQL
# :adapter: mysql
# :username: 'killbill'
# :password: 'killbill'
# :database: 'killbill' # or set the URL :
# #:url: jdbc:mysql://127.0.0.1:3306/killbill
# :driver: org.mariadb.jdbc.Driver # as in KB
# :pool: 30 # AR's default is max 5 connections
# In Kill Bill
# :adapter: mysql
# :jndi: 'killbill/osgi/jdbc'
# :pool: false # false-pool (JNDI pool's max)
# # uncomment if pool does not support JDBC4 :
# #:connection_alive_sql: 'select 1'
# # MySQL adapter #configure_connection defaults :
# # @@SESSION.sql_auto_is_null = 0,
# # @@SESSION.wait_timeout = 2147483,
# # @@SESSION.sql_mode = 'STRICT_ALL_TABLES'
# # ... can be disabled (on AR-JDBC 1.4) using :
# :configure_connection: false
ddl 스키마를 데이터베이스에 생성한다.
Killbill api 를 통하여 테넌트마다 페이팔 계정의 시그네이쳐, 아이디, 패스워드를 등록해주도록 한다.
curl -v \
-X POST \
-u admin:password \
-H 'X-Killbill-ApiKey: bob' \
-H 'X-Killbill-ApiSecret: lazar' \
-H 'X-Killbill-CreatedBy: admin' \
-H 'Content-Type: text/plain' \
-d ':paypal_express:
:signature: "your-paypal-signature"
:login: "your-username-facilitator.something.com"
:password: "your-password"' \
http://127.0.0.1:8080/1.0/kb/tenants/uploadPluginConfig/killbill-paypal-express
이후 사용 튜토리얼은 here
대략적인 것은, 페이팔은 express-checkout 이라는 종량제 과금 모드가 있는데, 이 종량제 동의 페이지를 띄우기 위해서
curl -v \
-X POST \
-u admin:password \
-H 'Content-Type: application/json' \
-H 'X-Killbill-ApiKey:bob' \
-H 'X-Killbill-ApiSecret:lazar' \
-H 'X-Killbill-CreatedBy: creator' \
--data-binary '{
"kb_account_id": "<ACCOUNT_ID>",
"currency": "USD",
"options": {
"return_url": "http://www.google.com/?q=SUCCESS",
"cancel_return_url": "http://www.google.com/?q=FAILURE",
"billing_agreement": {
"description": "Your subscription"
}
}
}' \
"http://127.0.0.1:8080/plugins/killbill-paypal-express/1.0/setup-checkout"
ㄱ.위 옵션중 return_url 을 운영중인 사이트의 Servlet 페이지로 연결시킨다.
ㄴ.위를 호출하면 location 헤더에 페이팔 종량제 동의창이 나옴.
ㄷ.사용자가 종량제 동의를 하면 return_url 로 페이팔이 리다이렉트 시킴
ㄹ. return_url 의 서블릿은 리다이렉트 결과를 가지고, 성공일 경우 킬빌에 페이먼트 메소드를 업데이트 시킴
curl -v \
-X POST \
-u admin:password \
-H 'Content-Type: application/json' \
-H 'X-Killbill-ApiKey:bob' \
-H 'X-Killbill-ApiSecret:lazar' \
-H 'X-Killbill-CreatedBy: creator' \
--data-binary '{
"pluginName": "killbill-paypal-express",
"pluginInfo": {
"properties": [
{
"key": "token",
"value": "EC-20G53990M6953444J"
}
]
}
}' \
"http://127.0.0.1:8080/1.0/kb/accounts/<ACCOUNT_ID>/paymentMethods?isDefault=true"
ㅁ. 이제 페이먼트 메소드가 등록되었고 디폴트 메소드로 지정했으니, 해당 어카운트는 결제주기마다 페이팔을 통해 과금을 집행할것임. overDue 정책도 활성화 됨.
add on 추가시 plansAllowedInBundle 지정치 않으면 추가가 안되는 문제
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.