Skip to content

인보이스 생성 및 발송 설계서

SeungpilPark edited this page Mar 20, 2017 · 1 revision

플러그인 제작 1단계: 킬빌 이벤트 리스너 등록

이메일 발송이 일어나는 상황인, INVOICE_NOTIFICATION,INVOICE_PAYMENT_SUCCESS,INVOICE_PAYMENT_FAILED,SUBSCRIPTION_CANCEL 이벤트를 캐치하는 코드를 제작한다.

    @Override
    public void handleKillbillEvent(final ExtBusEvent killbillEvent) {

        if (!EVENTS_TO_CONSIDER.contains(killbillEvent.getEventType())) {
            return;
        }

        // TODO see https://github.com/killbill/killbill-platform/issues/5
        final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());

        try {
            final Account account = osgiKillbillAPI.getAccountUserApi().getAccountById(killbillEvent.getAccountId(), new EmailNotificationContext(killbillEvent.getTenantId()));
            final String to = account.getEmail();
            if (to == null) {
                logService.log(LogService.LOG_INFO, "Account " + account.getId() + " does not have an email address configured, skip...");
                return;
            }

            final EmailNotificationContext context = new EmailNotificationContext(killbillEvent.getTenantId());
            switch (killbillEvent.getEventType()) {
                case INVOICE_NOTIFICATION:
                    sendEmailForUpComingInvoice(account, killbillEvent, context);
                    break;

                case INVOICE_PAYMENT_SUCCESS:
                case INVOICE_PAYMENT_FAILED:
                    sendEmailForPayment(account, killbillEvent, context);
                    break;

                case SUBSCRIPTION_CANCEL:
                    sendEmailForCancelledSubscription(account, killbillEvent, context);
                    break;

                default:
                    break;
            }

            logService.log(LogService.LOG_INFO, String.format("Received event %s for object type = %s, id = %s",
                    killbillEvent.getEventType(), killbillEvent.getObjectType(), killbillEvent.getObjectId()));

        } catch (final AccountApiException e) {
            logService.log(LogService.LOG_WARNING, String.format("Unable to find account: %s", killbillEvent.getAccountId()), e);
        } catch (InvoiceApiException e) {
            logService.log(LogService.LOG_WARNING, String.format("Fail to retrieve invoice for account %s", killbillEvent.getAccountId()), e);
        } catch (SubscriptionApiException e) {
            logService.log(LogService.LOG_WARNING, String.format("Fail to retrieve subscription for account %s", killbillEvent.getAccountId()), e);
        } catch (PaymentApiException e) {
            logService.log(LogService.LOG_WARNING, String.format("Fail to send email for account %s", killbillEvent.getAccountId()), e);
        } catch (EmailException e) {
            logService.log(LogService.LOG_WARNING, String.format("Fail to send email for account %s", killbillEvent.getAccountId()), e);
        } catch (IOException e) {
            logService.log(LogService.LOG_WARNING, String.format("Fail to send email for account %s", killbillEvent.getAccountId()), e);
        } catch (TenantApiException e) {
            logService.log(LogService.LOG_WARNING, String.format("Fail to send email for account %s", killbillEvent.getAccountId()), e);
        } catch (IllegalArgumentException e) {
            logService.log(LogService.LOG_WARNING, e.getMessage(), e);
        } catch (MustacheException e) {
            logService.log(LogService.LOG_WARNING, e.getMessage(), e);
        } finally {
            Thread.currentThread().setContextClassLoader(previousClassLoader);
        }
    }

단계 2: 각 이벤트에 해당하는 테넌트 별 이메일 템플릿을 가져온다.

INVOICE_NOTIFICATION: 다음 결제 인보이스 알림에 대한 이메일

private void sendEmailForUpComingInvoice(final Account account, final ExtBusEvent killbillEvent, final TenantContext context) throws IOException, InvoiceApiException, EmailException, TenantApiException {

        Preconditions.checkArgument(killbillEvent.getEventType() == ExtBusEventType.INVOICE_NOTIFICATION, String.format("Unexpected event %s", killbillEvent.getEventType()));

        final String dryRunTimePropValue = configProperties.getString(INVOICE_DRY_RUN_TIME_PROPERTY);
        Preconditions.checkArgument(dryRunTimePropValue != null, String.format("Cannot find property %s", INVOICE_DRY_RUN_TIME_PROPERTY));

        final TimeSpan span = new TimeSpan(dryRunTimePropValue);

        final DateTime now = clock.getClock().getUTCNow();
        final DateTime targetDateTime = now.plus(span.getMillis());

        final PluginCallContext callContext = new PluginCallContext(EmailNotificationActivator.PLUGIN_NAME, now, context.getTenantId());
        final Invoice invoice = osgiKillbillAPI.getInvoiceUserApi().triggerInvoiceGeneration(account.getId(), new LocalDate(targetDateTime, account.getTimeZone()), NULL_DRY_RUN_ARGUMENTS, callContext);
        if (invoice != null) {
            final EmailContent emailContent = templateRenderer.generateEmailForUpComingInvoice(account, invoice, context);
            sendEmail(account, emailContent, context);
        }
    }

INVOICE_PAYMENT_SUCCESS,INVOICE_PAYMENT_FAILED : 결제 성공, 실패에 따른 이메일

private void sendEmailForPayment(final Account account, final ExtBusEvent killbillEvent, final TenantContext context) throws InvoiceApiException, IOException, EmailException, PaymentApiException, TenantApiException {
        final UUID invoiceId = killbillEvent.getObjectId();
        if (invoiceId == null) {
            return;
        }

        Preconditions.checkArgument(killbillEvent.getEventType() == ExtBusEventType.INVOICE_PAYMENT_FAILED || killbillEvent.getEventType() == ExtBusEventType.INVOICE_PAYMENT_SUCCESS, String.format("Unexpected event %s", killbillEvent.getEventType()));

        final Invoice invoice = osgiKillbillAPI.getInvoiceUserApi().getInvoice(invoiceId, context);
        if (invoice.getNumberOfPayments() == 0) {
            // Aborted payment? Maybe no default payment method...
            return;
        }
        final InvoicePayment invoicePayment = invoice.getPayments().get(invoice.getNumberOfPayments() - 1);

        final Payment payment = osgiKillbillAPI.getPaymentApi().getPayment(invoicePayment.getPaymentId(), false, false, ImmutableList.<PluginProperty>of(), context);
        final PaymentTransaction lastTransaction = payment.getTransactions().get(payment.getTransactions().size() - 1);

        if (lastTransaction.getTransactionType() != TransactionType.PURCHASE &&
                lastTransaction.getTransactionType() != TransactionType.REFUND) {
            // Ignore for now, but this is easy to add...
            return;
        }

        EmailContent emailContent = null;
        if (lastTransaction.getTransactionType() == TransactionType.REFUND && lastTransaction.getTransactionStatus() == TransactionStatus.SUCCESS) {
            emailContent = templateRenderer.generateEmailForPaymentRefund(account, lastTransaction, context);
        } else {
            if (lastTransaction.getTransactionType() == TransactionType.PURCHASE && lastTransaction.getTransactionStatus() == TransactionStatus.SUCCESS) {
                emailContent = templateRenderer.generateEmailForSuccessfulPayment(account, invoice, context);
            } else if (lastTransaction.getTransactionType() == TransactionType.PURCHASE && lastTransaction.getTransactionStatus() == TransactionStatus.PAYMENT_FAILURE) {
                emailContent = templateRenderer.generateEmailForFailedPayment(account, invoice, context);
            }
        }
        if (emailContent != null) {
            sendEmail(account, emailContent, context);
        }
    }

SUBSCRIPTION_CANCEL : 구독 취소시 발송되는 이메일

private void sendEmailForCancelledSubscription(final Account account, final ExtBusEvent killbillEvent, final TenantContext context) throws SubscriptionApiException, IOException, EmailException, TenantApiException {
        Preconditions.checkArgument(killbillEvent.getEventType() == ExtBusEventType.SUBSCRIPTION_CANCEL, String.format("Unexpected event %s", killbillEvent.getEventType()));
        final UUID subscriptionId = killbillEvent.getObjectId();

        final Subscription subscription = osgiKillbillAPI.getSubscriptionApi().getSubscriptionForEntitlementId(subscriptionId, context);
        if (subscription != null) {
            final EmailContent emailContent = subscription.getState() == Entitlement.EntitlementState.CANCELLED ?
                    templateRenderer.generateEmailForSubscriptionCancellationEffective(account, subscription, context) :
                    templateRenderer.generateEmailForSubscriptionCancellationRequested(account, subscription, context);
            sendEmail(account, emailContent, context);
        }
    }

템플릿을 가져와서, 어카운트, 구독정보, 결제정보를 치환해주도록 해야한다.

private EmailContent getEmailContent(final TemplateType templateType, final AccountData account, @Nullable Subscription subscription, @Nullable final Invoice invoice, @Nullable final PaymentTransaction paymentTransaction, final TenantContext context) throws IOException, TenantApiException {

        final String accountLocale = Strings.emptyToNull(account.getLocale());
        final Locale locale = accountLocale == null ? Locale.getDefault() : LocaleUtils.toLocale(accountLocale);

        final Map<String, Object> data = new HashMap<String, Object>();
        final Map<String, String> text = getTranslationMap(accountLocale, ResourceBundleFactory.ResourceBundleType.TEMPLATE_TRANSLATION, context);
        data.put("text", text);
        data.put("account", account);
        if (subscription != null) {
            data.put("subscription", subscription);
        }
        if (invoice != null) {
            final InvoiceFormatter formattedInvoice = new DefaultInvoiceFormatter(text, invoice, locale);
            data.put("invoice", formattedInvoice);
        }
        if (paymentTransaction != null) {
            final PaymentFormatter formattedPayment = new PaymentFormatter(paymentTransaction, locale);
            data.put("payment", formattedPayment);
        }

        final String templateText = getTemplateText(locale, templateType, context);
        final String body = templateEngine.executeTemplateText(templateText, data);
        final String subject = new StringBuffer((String) text.get("merchantName")).append(": ").append(text.get(templateType.getSubjectKeyName())).toString();
        return new EmailContent(subject, body);
    }
Clone this wiki locally