| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965 |
- const OError = require('@overleaf/o-error')
- const request = require('request')
- const Settings = require('@overleaf/settings')
- const xml2js = require('xml2js')
- const logger = require('@overleaf/logger')
- const Errors = require('../Errors/Errors')
- const SubscriptionErrors = require('./Errors')
- const { callbackify } = require('@overleaf/promise-utils')
- async function updateAccountEmailAddress(accountId, newEmail) {
- const data = {
- email: newEmail,
- }
- let requestBody
- try {
- requestBody = RecurlyWrapper._buildXml('account', data)
- } catch (error) {
- throw OError.tag(error, 'error building xml', { accountId, newEmail })
- }
- const { body } = await RecurlyWrapper.promises.apiRequest({
- url: `accounts/${accountId}`,
- method: 'PUT',
- body: requestBody,
- })
- return await RecurlyWrapper.promises._parseAccountXml(body)
- }
- const promises = {
- _paypal: {
- async checkAccountExists(cache) {
- const { user } = cache
- logger.debug(
- { userId: user._id },
- 'checking if recurly account exists for user'
- )
- let response, body
- try {
- ;({ response, body } = await RecurlyWrapper.promises.apiRequest({
- url: `accounts/${user._id}`,
- method: 'GET',
- expect404: true,
- }))
- } catch (error) {
- OError.tag(
- error,
- 'error response from recurly while checking account',
- {
- user_id: user._id,
- }
- )
- throw error
- }
- if (response.statusCode === 404) {
- // actually not an error in this case, just no existing account
- logger.debug(
- { userId: user._id },
- 'user does not currently exist in recurly, proceed'
- )
- cache.userExists = false
- return cache
- }
- logger.debug({ userId: user._id }, 'user appears to exist in recurly')
- try {
- const account = await RecurlyWrapper.promises._parseAccountXml(body)
- cache.userExists = true
- cache.account = account
- return cache
- } catch (err) {
- OError.tag(err, 'error parsing account', {
- user_id: user._id,
- })
- throw err
- }
- },
- async createAccount(cache) {
- const { user } = cache
- const { subscriptionDetails } = cache
- if (cache.userExists) {
- return cache
- }
- const address = getAddressFromSubscriptionDetails(
- subscriptionDetails,
- false
- )
- const data = {
- account_code: user._id,
- email: user.email,
- first_name: user.first_name,
- last_name: user.last_name,
- address,
- }
- let requestBody
- try {
- requestBody = RecurlyWrapper._buildXml('account', data)
- } catch (error) {
- throw OError.tag(error, 'error building xml', { user_id: user._id })
- }
- let body
- try {
- ;({ body } = await RecurlyWrapper.promises.apiRequest({
- url: 'accounts',
- method: 'POST',
- body: requestBody,
- }))
- } catch (error) {
- OError.tag(
- error,
- 'error response from recurly while creating account',
- { user_id: user._id }
- )
- throw error
- }
- try {
- cache.account = await RecurlyWrapper.promises._parseAccountXml(body)
- return cache
- } catch (err) {
- OError.tag(err, 'error creating account', {
- user_id: user._id,
- })
- throw err
- }
- },
- async createBillingInfo(cache) {
- const { user } = cache
- const { recurlyTokenIds } = cache
- logger.debug({ userId: user._id }, 'creating billing info in recurly')
- const accountCode = cache?.account?.account_code
- if (!accountCode) {
- throw new Error('no account code at createBillingInfo stage')
- }
- const data = { token_id: recurlyTokenIds.billing }
- let requestBody
- try {
- requestBody = RecurlyWrapper._buildXml('billing_info', data)
- } catch (error) {
- throw OError.tag(error, 'error building xml', { user_id: user._id })
- }
- let body
- try {
- ;({ body } = await RecurlyWrapper.promises.apiRequest({
- url: `accounts/${accountCode}/billing_info`,
- method: 'POST',
- body: requestBody,
- }))
- } catch (error) {
- OError.tag(
- error,
- 'error response from recurly while creating billing info',
- { user_id: user._id }
- )
- throw error
- }
- try {
- cache.billingInfo =
- await RecurlyWrapper.promises._parseBillingInfoXml(body)
- return cache
- } catch (err) {
- OError.tag(err, 'error creating billing info', {
- user_id: user._id,
- accountCode,
- })
- throw err
- }
- },
- async setAddressAndCompanyBillingInfo(cache) {
- const { user } = cache
- const { subscriptionDetails } = cache
- logger.debug(
- { userId: user._id },
- 'setting billing address and company info in recurly'
- )
- const accountCode = cache?.account?.account_code
- if (!accountCode) {
- throw new Error(
- 'no account code at setAddressAndCompanyBillingInfo stage'
- )
- }
- const addressAndCompanyBillingInfo = getAddressFromSubscriptionDetails(
- subscriptionDetails,
- true
- )
- let requestBody
- try {
- requestBody = RecurlyWrapper._buildXml(
- 'billing_info',
- addressAndCompanyBillingInfo
- )
- } catch (error) {
- throw OError.tag(error, 'error building xml', { user_id: user._id })
- }
- let body
- try {
- ;({ body } = await RecurlyWrapper.promises.apiRequest({
- url: `accounts/${accountCode}/billing_info`,
- method: 'PUT',
- body: requestBody,
- }))
- } catch (error) {
- OError.tag(error, 'error response from recurly while setting address', {
- user_id: user._id,
- })
- throw error
- }
- try {
- cache.billingInfo =
- await RecurlyWrapper.promises._parseBillingInfoXml(body)
- return cache
- } catch (err) {
- if (err) {
- OError.tag(err, 'error updating billing info', {
- user_id: user._id,
- })
- throw err
- }
- }
- },
- async createSubscription(cache) {
- const { user } = cache
- const { subscriptionDetails } = cache
- logger.debug({ userId: user._id }, 'creating subscription in recurly')
- const data = {
- plan_code: subscriptionDetails.plan_code,
- currency: subscriptionDetails.currencyCode,
- coupon_code: subscriptionDetails.coupon_code,
- account: {
- account_code: user._id,
- },
- }
- const customFields =
- getCustomFieldsFromSubscriptionDetails(subscriptionDetails)
- if (customFields) {
- data.custom_fields = customFields
- }
- let requestBody
- try {
- requestBody = RecurlyWrapper._buildXml('subscription', data)
- } catch (error) {
- throw OError.tag(error, 'error building xml', { user_id: user._id })
- }
- let body
- try {
- ;({ body } = await RecurlyWrapper.promises.apiRequest({
- url: 'subscriptions',
- method: 'POST',
- body: requestBody,
- }))
- } catch (error) {
- OError.tag(
- error,
- 'error response from recurly while creating subscription',
- { user_id: user._id }
- )
- throw error
- }
- try {
- cache.subscription =
- await RecurlyWrapper.promises._parseSubscriptionXml(body)
- return cache
- } catch (err) {
- OError.tag(err, 'error creating subscription', {
- user_id: user._id,
- })
- throw err
- }
- },
- },
- async _createPaypalSubscription(user, subscriptionDetails, recurlyTokenIds) {
- logger.debug(
- { userId: user._id },
- 'starting process of creating paypal subscription'
- )
- // We use waterfall through each of these actions in sequence
- // passing a `cache` object along the way. The cache is initialized
- // with required data, and `async.apply` to pass the cache to the first function
- const cache = { user, recurlyTokenIds, subscriptionDetails }
- let result
- try {
- result = await RecurlyWrapper.promises._paypal.checkAccountExists(cache)
- result = await RecurlyWrapper.promises._paypal.createAccount(result)
- result = await RecurlyWrapper.promises._paypal.createBillingInfo(result)
- result =
- await RecurlyWrapper.promises._paypal.setAddressAndCompanyBillingInfo(
- result
- )
- result = await RecurlyWrapper.promises._paypal.createSubscription(result)
- } catch (err) {
- OError.tag(err, 'error in paypal subscription creation process', {
- user_id: user._id,
- })
- throw err
- }
- if (!result.subscription) {
- const err = new Error('no subscription object in result')
- OError.tag(err, 'error in paypal subscription creation process', {
- user_id: user._id,
- })
- throw err
- }
- logger.debug(
- { userId: user._id },
- 'done creating paypal subscription for user'
- )
- return result.subscription
- },
- async _createCreditCardSubscription(
- user,
- subscriptionDetails,
- recurlyTokenIds
- ) {
- const data = {
- plan_code: subscriptionDetails.plan_code,
- currency: subscriptionDetails.currencyCode,
- coupon_code: subscriptionDetails.coupon_code,
- account: {
- account_code: user._id,
- email: user.email,
- first_name: subscriptionDetails.first_name || user.first_name,
- last_name: subscriptionDetails.last_name || user.last_name,
- billing_info: {
- token_id: recurlyTokenIds.billing,
- },
- },
- }
- if (recurlyTokenIds.threeDSecureActionResult) {
- data.account.billing_info.three_d_secure_action_result_token_id =
- recurlyTokenIds.threeDSecureActionResult
- }
- const customFields =
- getCustomFieldsFromSubscriptionDetails(subscriptionDetails)
- if (customFields) {
- data.custom_fields = customFields
- }
- let requestBody
- try {
- requestBody = RecurlyWrapper._buildXml('subscription', data)
- } catch (error) {
- throw OError.tag(error, 'error building xml', { user_id: user._id })
- }
- const { response, body } = await RecurlyWrapper.promises.apiRequest({
- url: 'subscriptions',
- method: 'POST',
- body: requestBody,
- expect422: true,
- })
- if (response.statusCode === 422) {
- return await RecurlyWrapper.promises._handle422Response(body)
- } else {
- return await RecurlyWrapper.promises._parseSubscriptionXml(body)
- }
- },
- async createSubscription(user, subscriptionDetails, recurlyTokenIds) {
- const { isPaypal } = subscriptionDetails
- logger.debug(
- { userId: user._id, isPaypal },
- 'setting up subscription in recurly'
- )
- const fn = isPaypal
- ? RecurlyWrapper.promises._createPaypalSubscription
- : RecurlyWrapper.promises._createCreditCardSubscription
- return fn(user, subscriptionDetails, recurlyTokenIds)
- },
- /**
- * @param options - the options to pass to the request library
- * @returns {Promise<{ response: unknown, body: string}>}
- */
- apiRequest(options) {
- options.url = RecurlyWrapper.apiUrl + '/' + options.url
- options.headers = {
- Authorization: `Basic ${Buffer.from(
- Settings.apis.recurly.apiKey
- ).toString('base64')}`,
- Accept: 'application/xml',
- 'Content-Type': 'application/xml; charset=utf-8',
- 'X-Api-Version': Settings.apis.recurly.apiVersion,
- }
- const { expect404, expect422 } = options
- delete options.expect404
- delete options.expect422
- return new Promise((resolve, reject) => {
- request(options, function (error, response, body) {
- if (
- !error &&
- response.statusCode !== 200 &&
- response.statusCode !== 201 &&
- response.statusCode !== 204 &&
- (response.statusCode !== 404 || !expect404) &&
- (response.statusCode !== 422 || !expect422)
- ) {
- if (options.headers.Authorization) {
- options.headers.Authorization = 'REDACTED'
- }
- logger.warn(
- {
- err: error,
- body,
- options,
- statusCode: response ? response.statusCode : undefined,
- },
- 'error returned from recurly'
- )
- error = new OError(
- `Recurly API returned with status code: ${response.statusCode}`,
- { statusCode: response.statusCode }
- )
- reject(error)
- }
- resolve({ response, body })
- })
- })
- },
- async getSubscriptions(accountId) {
- const { body } = await RecurlyWrapper.promises.apiRequest({
- url: `accounts/${accountId}/subscriptions`,
- })
- return await RecurlyWrapper.promises._parseXml(body)
- },
- async getSubscription(subscriptionId, options) {
- let url
- if (!options) {
- options = {}
- }
- if (options.recurlyJsResult) {
- url = `recurly_js/result/${subscriptionId}`
- } else {
- url = `subscriptions/${subscriptionId}`
- }
- const { body } = await RecurlyWrapper.promises.apiRequest({
- url,
- })
- const recurlySubscription =
- await RecurlyWrapper.promises._parseSubscriptionXml(body)
- if (options.includeAccount) {
- let accountId
- if (recurlySubscription.account && recurlySubscription.account.url) {
- accountId = recurlySubscription.account.url.match(/accounts\/(.*)/)[1]
- } else {
- throw new Error("I don't understand the response from Recurly")
- }
- recurlySubscription.account =
- await RecurlyWrapper.promises.getAccount(accountId)
- return recurlySubscription
- } else {
- return recurlySubscription
- }
- },
- /**
- * @typedef {{getNextPage: () => Promise<PageData>, items: any[]}} PageData
- */
- async getPaginatedEndpoint(resource, queryParams) {
- let allItems = []
- let items
- /** @type {() => Promise<PageData>} */
- let getNextPage = promises.getPaginatedEndpointIterator(
- resource,
- queryParams
- )
- while (getNextPage) {
- ;({ items, getNextPage } = await getNextPage())
- allItems = allItems.concat(items)
- logger.debug(`total now ${allItems.length}`)
- }
- return allItems
- },
- /**
- * @returns {() => Promise<PageData>}
- */
- getPaginatedEndpointIterator(resource, queryParams) {
- queryParams.per_page = queryParams.per_page || 200
- const getPage = async (cursor = null) => {
- const opts = {
- url: resource,
- qs: queryParams,
- }
- if (cursor) {
- opts.qs.cursor = cursor
- }
- const { response, body } = await RecurlyWrapper.promises.apiRequest(opts)
- const data = await RecurlyWrapper.promises._parseXml(body)
- const items = data[resource]
- logger.debug(`got ${items.length} items in this page`)
- const match = response.headers.link?.match(/cursor=([0-9.]+%3A[0-9.]+)&/)
- const nextCursor = match && match[1]
- return {
- items,
- getNextPage:
- nextCursor && (() => getPage(decodeURIComponent(nextCursor))),
- }
- }
- return getPage
- },
- async getAccount(accountId) {
- const { body } = await RecurlyWrapper.promises.apiRequest({
- url: `accounts/${accountId}`,
- })
- return await RecurlyWrapper.promises._parseAccountXml(body)
- },
- updateAccountEmailAddress,
- async getAccountActiveCoupons(accountId) {
- const { body } = await RecurlyWrapper.promises.apiRequest({
- url: `accounts/${accountId}/redemptions`,
- })
- const redemptions = await RecurlyWrapper.promises._parseRedemptionsXml(body)
- const activeRedemptions = redemptions.filter(
- redemption => redemption.state === 'active'
- )
- const couponCodes = activeRedemptions.map(
- redemption => redemption.coupon_code
- )
- return await Promise.all(
- couponCodes.map(couponCode =>
- RecurlyWrapper.promises.getCoupon(couponCode)
- )
- )
- },
- async getCoupon(couponCode) {
- const opts = { url: `coupons/${couponCode}` }
- const { body } = await RecurlyWrapper.promises.apiRequest(opts)
- return await RecurlyWrapper.promises._parseCouponXml(body)
- },
- async getBillingInfo(accountId) {
- const { body } = await RecurlyWrapper.promises.apiRequest({
- url: `accounts/${accountId}/billing_info`,
- })
- return await RecurlyWrapper.promises._parseXml(body)
- },
- async getAccountPastDueInvoices(accountId) {
- const { body } = await RecurlyWrapper.promises.apiRequest({
- url: `accounts/${accountId}/invoices?state=past_due`,
- })
- return await RecurlyWrapper.promises._parseInvoicesXml(body)
- },
- async attemptInvoiceCollection(invoiceId) {
- return await RecurlyWrapper.promises.apiRequest({
- url: `invoices/${invoiceId}/collect`,
- method: 'put',
- })
- },
- async updateSubscription(subscriptionId, options) {
- logger.debug(
- { subscriptionId, options },
- 'telling recurly to update subscription'
- )
- const data = {
- plan_code: options.plan_code,
- timeframe: options.timeframe,
- }
- let requestBody
- try {
- requestBody = RecurlyWrapper._buildXml('subscription', data)
- } catch (error) {
- throw OError.tag(error, 'error building xml', { subscriptionId })
- }
- const { body } = await RecurlyWrapper.promises.apiRequest({
- url: `subscriptions/${subscriptionId}`,
- method: 'put',
- body: requestBody,
- })
- return await RecurlyWrapper.promises._parseSubscriptionXml(body)
- },
- async createFixedAmmountCoupon(
- couponCode,
- name,
- currencyCode,
- discountInCents,
- planCode
- ) {
- const data = {
- coupon_code: couponCode,
- name,
- discount_type: 'dollars',
- discount_in_cents: {},
- plan_codes: {
- plan_code: planCode,
- },
- applies_to_all_plans: false,
- }
- data.discount_in_cents[currencyCode] = discountInCents
- let requestBody
- try {
- requestBody = RecurlyWrapper._buildXml('coupon', data)
- } catch (error) {
- throw OError.tag(error, 'error building xml', {
- couponCode,
- name,
- })
- }
- logger.debug({ couponCode, requestBody }, 'creating coupon')
- try {
- await RecurlyWrapper.promises.apiRequest({
- url: 'coupons',
- method: 'post',
- body: requestBody,
- })
- } catch (error) {
- logger.warn({ err: error, couponCode }, 'error creating coupon')
- throw error
- }
- },
- async lookupCoupon(couponCode) {
- const { body } = await RecurlyWrapper.promises.apiRequest({
- url: `coupons/${couponCode}`,
- })
- return await RecurlyWrapper.promises._parseCouponXml(body)
- },
- async redeemCoupon(accountCode, couponCode) {
- const data = {
- account_code: accountCode,
- currency: 'USD',
- }
- let requestBody
- try {
- requestBody = RecurlyWrapper._buildXml('redemption', data)
- } catch (error) {
- throw OError.tag(error, 'error building xml', {
- accountCode,
- couponCode,
- })
- }
- logger.debug(
- { accountCode, couponCode, requestBody },
- 'redeeming coupon for user'
- )
- try {
- await RecurlyWrapper.promises.apiRequest({
- url: `coupons/${couponCode}/redeem`,
- method: 'post',
- body: requestBody,
- })
- } catch (error) {
- logger.warn(
- { err: error, accountCode, couponCode },
- 'error redeeming coupon'
- )
- throw error
- }
- },
- async extendTrial(subscriptionId, daysUntilExpire) {
- if (daysUntilExpire == null) {
- daysUntilExpire = 7
- }
- const nextRenewalDate = new Date()
- nextRenewalDate.setDate(nextRenewalDate.getDate() + daysUntilExpire)
- logger.debug(
- { subscriptionId, daysUntilExpire },
- 'Exending Free trial for user'
- )
- try {
- await RecurlyWrapper.promises.apiRequest({
- url: `/subscriptions/${subscriptionId}/postpone?next_bill_date=${nextRenewalDate}&bulk=false`,
- method: 'put',
- })
- } catch (error) {
- logger.warn(
- { err: error, subscriptionId, daysUntilExpire },
- 'error exending trial'
- )
- throw error
- }
- },
- async listAccountActiveSubscriptions(accountId) {
- const { response, body } = await RecurlyWrapper.promises.apiRequest({
- url: `accounts/${accountId}/subscriptions`,
- qs: {
- state: 'active',
- },
- expect404: true,
- })
- if (response.statusCode === 404) {
- return []
- } else {
- return await RecurlyWrapper.promises._parseSubscriptionsXml(body)
- }
- },
- async _handle422Response(body) {
- const data = await RecurlyWrapper.promises._parseErrorsXml(body)
- let errorData = {}
- if (data.transaction_error) {
- errorData = {
- message: data.transaction_error.merchant_message,
- info: {
- category: data.transaction_error.error_category,
- gatewayCode: data.transaction_error.gateway_error_code,
- public: {
- code: data.transaction_error.error_code,
- message: data.transaction_error.customer_message,
- },
- },
- }
- if (data.transaction_error.three_d_secure_action_token_id) {
- errorData.info.public.threeDSecureActionTokenId =
- data.transaction_error.three_d_secure_action_token_id
- }
- } else if (data.error && data.error._) {
- // fallback for errors that don't have a `transaction_error` field, but
- // instead a `error` field with a message (e.g. VATMOSS errors)
- errorData = {
- info: {
- public: {
- message: data.error._,
- },
- },
- }
- }
- throw new SubscriptionErrors.RecurlyTransactionError(errorData)
- },
- async _parseSubscriptionsXml(xml) {
- return await RecurlyWrapper.promises._parseXmlAndGetAttribute(
- xml,
- 'subscriptions'
- )
- },
- async _parseSubscriptionXml(xml) {
- return await RecurlyWrapper.promises._parseXmlAndGetAttribute(
- xml,
- 'subscription'
- )
- },
- async _parseAccountXml(xml) {
- return await RecurlyWrapper.promises._parseXmlAndGetAttribute(
- xml,
- 'account'
- )
- },
- async _parseBillingInfoXml(xml) {
- return await RecurlyWrapper.promises._parseXmlAndGetAttribute(
- xml,
- 'billing_info'
- )
- },
- async _parseRedemptionsXml(xml) {
- return await RecurlyWrapper.promises._parseXmlAndGetAttribute(
- xml,
- 'redemptions'
- )
- },
- async _parseCouponXml(xml) {
- return await RecurlyWrapper.promises._parseXmlAndGetAttribute(xml, 'coupon')
- },
- async _parseErrorsXml(xml) {
- return await RecurlyWrapper.promises._parseXmlAndGetAttribute(xml, 'errors')
- },
- async _parseInvoicesXml(xml) {
- return await RecurlyWrapper.promises._parseXmlAndGetAttribute(
- xml,
- 'invoices'
- )
- },
- async _parseXmlAndGetAttribute(xml, attribute) {
- const data = await RecurlyWrapper.promises._parseXml(xml)
- if (data && data[attribute] != null) {
- return data[attribute]
- } else {
- throw new Error("I don't understand the response from Recurly")
- }
- },
- _parseXml(xml) {
- function convertDataTypes(data) {
- let key, value
- if (data && data.$) {
- if (data.$.nil === 'nil') {
- data = null
- } else if (data.$.href) {
- data.url = data.$.href
- delete data.$
- } else if (data.$.type === 'integer') {
- data = parseInt(data._, 10)
- } else if (data.$.type === 'datetime') {
- data = new Date(data._)
- } else if (data.$.type === 'array') {
- delete data.$
- let array = []
- for (key in data) {
- value = data[key]
- if (value instanceof Array) {
- array = array.concat(convertDataTypes(value))
- } else {
- array.push(convertDataTypes(value))
- }
- }
- data = array
- }
- }
- if (data instanceof Array) {
- data = data.map(entry => convertDataTypes(entry))
- } else if (typeof data === 'object') {
- for (key in data) {
- value = data[key]
- data[key] = convertDataTypes(value)
- }
- }
- return data
- }
- const parser = new xml2js.Parser({
- explicitRoot: true,
- explicitArray: false,
- emptyTag: '',
- })
- return new Promise((resolve, reject) =>
- parser.parseString(xml, function (error, data) {
- if (error) {
- return reject(error)
- }
- const result = convertDataTypes(data)
- resolve(result)
- })
- )
- },
- }
- function _buildXml(rootName, data) {
- const options = {
- headless: true,
- renderOpts: {
- pretty: true,
- indent: '\t',
- },
- rootName,
- }
- const builder = new xml2js.Builder(options)
- return builder.buildObject(data)
- }
- const RecurlyWrapper = {
- apiUrl: Settings.apis.recurly.url || 'https://api.recurly.com/v2',
- _buildXml,
- _parseXml: callbackify(promises._parseXml),
- createFixedAmmountCoupon: callbackify(promises.createFixedAmmountCoupon),
- getAccountActiveCoupons: callbackify(promises.getAccountActiveCoupons),
- getBillingInfo: callbackify(promises.getBillingInfo),
- getPaginatedEndpoint: callbackify(promises.getPaginatedEndpoint),
- getSubscription: callbackify(promises.getSubscription),
- getSubscriptions: callbackify(promises.getSubscriptions),
- updateAccountEmailAddress: callbackify(promises.updateAccountEmailAddress),
- }
- RecurlyWrapper.promises = {
- ...promises,
- updateAccountEmailAddress,
- }
- module.exports = RecurlyWrapper
- function getCustomFieldsFromSubscriptionDetails(subscriptionDetails) {
- if (!subscriptionDetails.ITMCampaign) {
- return null
- }
- const customFields = [
- {
- name: 'itm_campaign',
- value: subscriptionDetails.ITMCampaign,
- },
- ]
- if (subscriptionDetails.ITMContent) {
- customFields.push({
- name: 'itm_content',
- value: subscriptionDetails.ITMContent,
- })
- }
- if (subscriptionDetails.ITMReferrer) {
- customFields.push({
- name: 'itm_referrer',
- value: subscriptionDetails.ITMReferrer,
- })
- }
- return { custom_field: customFields }
- }
- function getAddressFromSubscriptionDetails(
- subscriptionDetails,
- includeCompanyInfo
- ) {
- const { address } = subscriptionDetails
- if (!address || !address.country) {
- throw new Errors.InvalidError({
- message: 'Invalid country',
- info: {
- public: {
- message: 'Invalid country',
- },
- },
- })
- }
- const addressObject = {
- address1: address.address1,
- address2: address.address2 || '',
- city: address.city || '',
- state: address.state || '',
- zip: address.zip || '',
- country: address.country,
- }
- if (
- includeCompanyInfo &&
- subscriptionDetails.billing_info &&
- subscriptionDetails.billing_info.company &&
- subscriptionDetails.billing_info.company !== ''
- ) {
- addressObject.company = subscriptionDetails.billing_info.company
- if (
- subscriptionDetails.billing_info.vat_number &&
- subscriptionDetails.billing_info.vat_number !== ''
- ) {
- addressObject.vat_number = subscriptionDetails.billing_info.vat_number
- }
- }
- return addressObject
- }
|