Giter Site home page Giter Site logo

theopencloudengine / killbill Goto Github PK

View Code? Open in Web Editor NEW

This project forked from killbill/killbill

1.0 1.0 2.0 647.68 MB

Open-Source Subscription Billing & Payment Platform

Home Page: http://killbill.io/

Java 95.21% Shell 0.22% Ruby 0.08% HTML 0.19% CSS 1.82% JavaScript 2.40% PLpgSQL 0.01% SQLPL 0.08%

killbill's People

Contributors

alenad avatar andrenpaes avatar beccagaspard avatar brianm avatar consulthys avatar daliwei avatar darth30joker avatar holkra avatar kares avatar kevinpostlewaite avatar killbillio avatar marksimu avatar martinwesthead avatar matias-aguero-hs avatar maxgfeller avatar pierre avatar raygrpn avatar sbrossie avatar seungpilpark avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Forkers

ljxu1 cissp0

killbill's Issues

산출물 등록

11월 작업까지
12월 작업까지

설계 및 연계 관련, 각 WBS 항목별 산출물 문서 작성

Catalog 만들기 - overrage charge

100 메가 이상된 사용량에 대하여 월별로 어떻게 계산

  • 적분량..을 기반으로하느냐.. 평균 사용량으로 하느냐.. --> 이건 비즈니스 로직이라고 하자.. 총량을 계산하는 방법은...
  • 그량을 기반으로 tiered pricing 되면 그 값을 넣는다.
  • app 비즈니스 로직에 담아야 하면 app 을 수정해야 한다.
  • 고객마다 기본 무료 사용량이 다름
  • overrage charge

Maven build fail with dynamic catalog provider

톰캣의 Jvm 프로퍼티에 다음의 값이 설정되어있으면 런타임시 동적카달로그 배포하도록 함.

-Dorg.killbill.catalog.mode=dynamic

Maven 빌드 실행시에는 test 용 defaultCatalog 배포로 정상적으로 빌드됨.

미터링 시스템과 KB 간 ETL 연동

  • data 를 어떻게 load 하는가? 증분해서 넣느냐
  • 매일 통째로 넣느냐
  • amount 를 batch 로 넣다가보면 쌓이는데
  • amount 를 조정하는 API 를 찾거나 확장하거나..
  • tx 이슈 혹은 update 할 수 있는 방법을 찾던

우분투 환경에서 실행시 행걸림

우분투 환경에서 KPM으로 설치시, 서버가 초반 몇번의 트랜잭션만 받고 행이 걸림.

이후 간간히 행이 풀렸다 걸렸다 하다 완전 무응답 상태로 돌입하고, 의미있는 로그가 남지 않음.

KPM 설치된 모듈의 디버깅 방법을 알기.

Understanding the Bundle Structure

  1. How to query bundle grouping related to activate subscriptions for an account?
  2. How bundle is changed or created during a subscription is created?

서브스크립션 벌크 생성시... bus_events 및 서버 행걸림

중요

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

Docker 환경에서의 paypal 플러그인 에러

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);
            }
        }
    }

카탈로그 생성 코드

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만명 등록
    }
}

Inovice 를 출력

  • 추가 property 를 어떻게 출력할 것인가
  • usage 의 description 이나 comment 부분에 어떻게 출력하게 하는지

Catalog 만들기 - Standalone product

Addon / Standalone (product type) 처리

  • addon 을 등록해주기 위하여 Catalog XML을 동적으로 변경하여 다시 deploy 시키는 기능이 필요
    ** Add-on 에 대하여 BASE 와 통합된 invoice 내에 넣어서 줌.
  • Standalone 으로 10000개 정도의 상품이 디플로이되고 과금하는데 성능저하가 없는지부터확인

다국어 처리

invoice 등에 출력되는 텍스트의 다국어 처리 (i.e. adjustment...)

페이팔 설치및 계정세팅

소스코드 : here

  1. ㄱ. kaui 에서 인스톨 과정
    ㄴ. 또는 커스텀 빌드

  2. 프로덕션 모드 세팅
    /var/tmp/bundles/plugins/ruby/killbill-paypal-express/5.0.0/ROOT 폴더에 paypal_express.yml 파일이 있다.

:paypal_express:
  :test: false
  1. DockerFile 로 생성할 경우 kaui 에서 세팅할 수 없으므로 Configrutaion 에 해당하는 paypal_express.yml 를 직접 생성해 넣는 로직을 짜도록 하자.
: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
  1. ddl 스키마를 데이터베이스에 생성한다.

  2. 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 정책도 활성화 됨.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.