From af6387bbf33cafce303b2eafb5ab5dd4cb3aa88a Mon Sep 17 00:00:00 2001 From: James Grugett Date: Fri, 7 Jan 2022 16:56:14 -0600 Subject: [PATCH] Stripe (#22) * basic working payments * Select funds amount and prettier funds button / dialog * Add funds page and nav menu option * Format funds amount. Use ghost button for back. * Add mantic dollars description * Improve styles of add funds page * about styling * change faq => about * change default font to Courier * header sign out menu item; remove user card * keep logo font * fix header issue * stripe webhook: handle repeat events * Make add funds button a gradient * add funds referer url * Fix add funds page after merge * Slight VisD tweaks * Update add funds button position. Mantic => Manifold * Remove Add funds menu option for now. * Set up product ids and endpoint for stripe prod * Swap back order in profile menu Co-authored-by: mantikoros Co-authored-by: Austin Chen --- functions/package.json | 3 +- functions/src/index.ts | 1 + functions/src/stripe.ts | 126 ++++++++++++++++++++++++++++ functions/yarn.lock | 65 +++++++++++++- web/components/add-funds-button.tsx | 76 +++++++++++++++++ web/components/bet-panel.tsx | 2 + web/components/profile-menu.tsx | 4 + web/components/yes-no-selector.tsx | 28 +++++++ web/lib/service/stripe.ts | 13 +++ web/pages/add-funds.tsx | 68 +++++++++++++++ web/public/praying-mantis-light.svg | 67 +++++++++++++++ 11 files changed, 451 insertions(+), 2 deletions(-) create mode 100644 functions/src/stripe.ts create mode 100644 web/components/add-funds-button.tsx create mode 100644 web/lib/service/stripe.ts create mode 100644 web/pages/add-funds.tsx create mode 100644 web/public/praying-mantis-light.svg diff --git a/functions/package.json b/functions/package.json index 79de1b65..83abac53 100644 --- a/functions/package.json +++ b/functions/package.json @@ -18,7 +18,8 @@ "firebase-admin": "10.0.0", "firebase-functions": "3.16.0", "lodash": "4.17.21", - "mailgun-js": "0.22.0" + "mailgun-js": "0.22.0", + "stripe": "8.194.0" }, "devDependencies": { "@types/mailgun-js": "0.22.12", diff --git a/functions/src/index.ts b/functions/src/index.ts index 088ca534..cd2d429a 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -5,5 +5,6 @@ admin.initializeApp() // export * from './keep-awake' export * from './place-bet' export * from './resolve-market' +export * from './stripe' export * from './sell-bet' export * from './create-contract' diff --git a/functions/src/stripe.ts b/functions/src/stripe.ts new file mode 100644 index 00000000..aac128cb --- /dev/null +++ b/functions/src/stripe.ts @@ -0,0 +1,126 @@ +import * as functions from 'firebase-functions' +import * as admin from 'firebase-admin' +import Stripe from 'stripe' + +import { payUser } from './resolve-market' + +const stripe = new Stripe(functions.config().stripe.apikey, { + apiVersion: '2020-08-27', + typescript: true, +}) + +// manage at https://dashboard.stripe.com/test/products?active=true +const manticDollarStripePrice = + admin.instanceId().app.options.projectId === 'mantic-markets' + ? { + 500: 'price_1KFQXcGdoFKoCJW770gTNBrm', + 1000: 'price_1KFQp1GdoFKoCJW7Iu0dsF65', + 2500: 'price_1KFQqNGdoFKoCJW7SDvrSaEB', + 10000: 'price_1KFQraGdoFKoCJW77I4XCwM3', + } + : { + 500: 'price_1K8W10GdoFKoCJW7KWORLec1', + 1000: 'price_1K8bC1GdoFKoCJW76k3g5MJk', + 2500: 'price_1K8bDSGdoFKoCJW7avAwpV0e', + 10000: 'price_1K8bEiGdoFKoCJW7Us4UkRHE', + } + +export const createCheckoutSession = functions + .runWith({ minInstances: 1 }) + .https.onRequest(async (req, res) => { + const userId = req.query.userId?.toString() + + const manticDollarQuantity = req.query.manticDollarQuantity?.toString() + + if (!userId) { + res.status(400).send('Invalid user ID') + return + } + + if ( + !manticDollarQuantity || + !Object.keys(manticDollarStripePrice).includes(manticDollarQuantity) + ) { + res.status(400).send('Invalid Mantic Dollar quantity') + return + } + + const referrer = + req.query.referer || req.headers.referer || 'https://mantic.markets' + + const session = await stripe.checkout.sessions.create({ + metadata: { + userId, + manticDollarQuantity, + }, + line_items: [ + { + price: + manticDollarStripePrice[ + manticDollarQuantity as unknown as keyof typeof manticDollarStripePrice + ], + quantity: 1, + }, + ], + mode: 'payment', + success_url: `${referrer}?funding-success`, + cancel_url: `${referrer}?funding-failiure`, + }) + + res.redirect(303, session.url || '') + }) + +export const stripeWebhook = functions + .runWith({ minInstances: 1 }) + .https.onRequest(async (req, res) => { + let event + + try { + event = stripe.webhooks.constructEvent( + req.rawBody, + req.headers['stripe-signature'] as string, + functions.config().stripe.webhooksecret + ) + } catch (e: any) { + console.log(`Webhook Error: ${e.message}`) + res.status(400).send(`Webhook Error: ${e.message}`) + return + } + + if (event.type === 'checkout.session.completed') { + const session = event.data.object as any + await issueMoneys(session) + } + + res.status(200).send('success') + }) + +const issueMoneys = async (session: any) => { + const { id: sessionId } = session + + const query = await firestore + .collection('stripe-transactions') + .where('sessionId', '==', sessionId) + .get() + + if (!query.empty) { + console.log('session', sessionId, 'already processed') + return + } + + const { userId, manticDollarQuantity } = session.metadata + const payout = Number.parseInt(manticDollarQuantity) + + await firestore.collection('stripe-transactions').add({ + userId, + manticDollarQuantity: payout, // save as number + sessionId, + session, + }) + + await payUser([userId, payout]) + + console.log('user', userId, 'paid M$', payout) +} + +const firestore = admin.firestore() diff --git a/functions/yarn.lock b/functions/yarn.lock index 6cec7f41..5a958ea1 100644 --- a/functions/yarn.lock +++ b/functions/yarn.lock @@ -314,7 +314,7 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0": +"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=8.1.0": version "17.0.1" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.1.tgz#88d501e84b6185f6489ecee4ba9e8fcec7f29bb2" integrity sha512-NXKvBVUzIbs6ylBwmOwHFkZS2EXCcjnqr8ZCRNaXBkHAf+3mn/rPcJxwrzuc6movh8fxQAsUUfYklJ/EG+hZqQ== @@ -464,6 +464,14 @@ bytes@3.1.1: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + cliui@^7.0.2: version "7.0.4" resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" @@ -895,6 +903,11 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + ftp@~0.3.10: version "0.3.10" resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" @@ -946,6 +959,15 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -1018,6 +1040,18 @@ gtoken@^5.0.4: google-p12-pem "^3.0.3" jws "^4.0.0" +has-symbols@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + hash-stream-validation@^0.2.2: version "0.2.4" resolved "https://registry.yarnpkg.com/hash-stream-validation/-/hash-stream-validation-0.2.4.tgz#ee68b41bf822f7f44db1142ec28ba9ee7ccb7512" @@ -1428,6 +1462,11 @@ object-hash@^2.1.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== +object-inspect@^1.9.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -1605,6 +1644,13 @@ qs@6.9.6: resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== +qs@^6.6.0: + version "6.10.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.2.tgz#c1431bea37fc5b24c5bdbafa20f16bdf2a4b9ffe" + integrity sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw== + dependencies: + side-channel "^1.0.4" + range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -1729,6 +1775,15 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + signal-exit@^3.0.2: version "3.0.6" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" @@ -1822,6 +1877,14 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +stripe@8.194.0: + version "8.194.0" + resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.194.0.tgz#67fc7a34260f95f9103834a1f0962d27c608cf73" + integrity sha512-iERByJUNA7sdkfQ3fD1jcrAZqPxCtTmL2EUzvHUVLXyoacDrflkq4ux5KFxYhfCIerrOAhquVj17+sBHn96/Kg== + dependencies: + "@types/node" ">=8.1.0" + qs "^6.6.0" + stubs@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" diff --git a/web/components/add-funds-button.tsx b/web/components/add-funds-button.tsx new file mode 100644 index 00000000..3c70604e --- /dev/null +++ b/web/components/add-funds-button.tsx @@ -0,0 +1,76 @@ +import clsx from 'clsx' +import { useState } from 'react' + +import { useUser } from '../hooks/use-user' +import { checkoutURL } from '../lib/service/stripe' +import { FundsSelector } from './yes-no-selector' + +export function AddFundsButton(props: { className?: string }) { + const { className } = props + const user = useUser() + + const [amountSelected, setAmountSelected] = useState< + 500 | 1000 | 2500 | 10000 + >(500) + + return ( + <> + + + +
+
+
Get Manifold Dollars
+ +
+ Use Manifold Dollars to trade in your favorite markets.
(Not + redeemable for cash.) +
+ +
Amount
+ + +
+
Price USD
+
+ ${Math.round(amountSelected / 100)}.00 +
+
+ +
+ + +
+ +
+
+
+
+ + ) +} diff --git a/web/components/bet-panel.tsx b/web/components/bet-panel.tsx index bf92f033..a8d52e1b 100644 --- a/web/components/bet-panel.tsx +++ b/web/components/bet-panel.tsx @@ -21,6 +21,7 @@ import { calculatePayoutAfterCorrectBet, } from '../lib/calculate' import { firebaseLogin } from '../lib/firebase/users' +import { AddFundsButton } from './add-funds-button' import { OutcomeLabel } from './outcome-label' import { AdvancedPanel } from './advanced-panel' import { Bet } from '../lib/firebase/bets' @@ -149,6 +150,7 @@ export function BetPanel(props: { contract: Contract; className?: string }) { {error} )} + {user && }
Implied probability
diff --git a/web/components/profile-menu.tsx b/web/components/profile-menu.tsx index 2c8ad379..9186568b 100644 --- a/web/components/profile-menu.tsx +++ b/web/components/profile-menu.tsx @@ -48,6 +48,10 @@ function getNavigationOptions(user: User, options: { mobile: boolean }) { name: 'Your markets', href: `/${user.username}`, }, + // { + // name: 'Add funds', + // href: '/add-funds', + // }, { name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh', diff --git a/web/components/yes-no-selector.tsx b/web/components/yes-no-selector.tsx index 528b199d..9e31cdd6 100644 --- a/web/components/yes-no-selector.tsx +++ b/web/components/yes-no-selector.tsx @@ -1,5 +1,6 @@ import clsx from 'clsx' import React from 'react' +import { formatMoney } from '../lib/util/format' import { Col } from './layout/col' import { Row } from './layout/row' @@ -80,6 +81,33 @@ export function YesNoCancelSelector(props: { ) } +const fundAmounts = [500, 1000, 2500, 10000] + +export function FundsSelector(props: { + selected: 500 | 1000 | 2500 | 10000 + onSelect: (selected: 500 | 1000 | 2500 | 10000) => void + className?: string + btnClassName?: string +}) { + const { selected, onSelect, className } = props + const btnClassName = clsx('px-2 whitespace-nowrap', props.btnClassName) + + return ( + + {fundAmounts.map((amount) => ( + + ))} + + ) +} + function Button(props: { className?: string onClick?: () => void diff --git a/web/lib/service/stripe.ts b/web/lib/service/stripe.ts new file mode 100644 index 00000000..a5f6c4a0 --- /dev/null +++ b/web/lib/service/stripe.ts @@ -0,0 +1,13 @@ +import { isProd } from '../firebase/init' + +export const checkoutURL = ( + userId: string, + manticDollarQuantity: number, + referer = '' +) => { + const endpoint = isProd + ? 'https://us-central1-mantic-markets.cloudfunctions.net/createCheckoutSession' + : 'https://us-central1-dev-mantic-markets.cloudfunctions.net/createCheckoutSession' + + return `${endpoint}?userId=${userId}&manticDollarQuantity=${manticDollarQuantity}&referer=${referer}` +} diff --git a/web/pages/add-funds.tsx b/web/pages/add-funds.tsx new file mode 100644 index 00000000..0a55c364 --- /dev/null +++ b/web/pages/add-funds.tsx @@ -0,0 +1,68 @@ +import { useState } from 'react' +import { Col } from '../components/layout/col' +import { SEO } from '../components/SEO' +import { Title } from '../components/title' +import { FundsSelector } from '../components/yes-no-selector' +import { useUser } from '../hooks/use-user' +import { checkoutURL } from '../lib/service/stripe' +import Image from 'next/image' +import { Spacer } from '../components/layout/spacer' +import { Page } from '../components/page' + +export default function AddFundsPage() { + const user = useUser() + + const [amountSelected, setAmountSelected] = useState< + 500 | 1000 | 2500 | 10000 + >(500) + + return ( + + + + + + + <Image + className="block mt-6" + src="/praying-mantis-light.svg" + width={200} + height={200} + /> + + <div className="text-gray-500 mb-6"> + Use Manifold Dollars to trade in your favorite markets. <br /> (Not + redeemable for cash.) + </div> + + <div className="text-gray-500 text-sm mb-2">Amount</div> + <FundsSelector + className="max-w-md" + selected={amountSelected} + onSelect={setAmountSelected} + /> + + <div className="mt-6"> + <div className="text-gray-500 text-sm mb-1">Price USD</div> + <div className="text-xl"> + ${Math.round(amountSelected / 100)}.00 + </div> + </div> + + <form + action={checkoutURL(user?.id || '', amountSelected)} + method="POST" + className="mt-12" + > + <button + type="submit" + className="btn btn-primary w-full font-medium bg-gradient-to-r from-teal-500 to-green-500 hover:from-teal-600 hover:to-green-600" + > + Checkout + </button> + </form> + </Col> + </Col> + </Page> + ) +} diff --git a/web/public/praying-mantis-light.svg b/web/public/praying-mantis-light.svg new file mode 100644 index 00000000..cc82cd53 --- /dev/null +++ b/web/public/praying-mantis-light.svg @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="svg2" + xml:space="preserve" + width="2829.3333" + height="2829.3333" + viewBox="0 0 2829.3333 2829.3333" + sodipodi:docname="shutterstock_2073140717.eps"><metadata + id="metadata8"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs + id="defs6" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="640" + inkscape:window-height="480" + id="namedview4" /><g + id="g10" + inkscape:groupmode="layer" + inkscape:label="ink_ext_XXXXXX" + transform="matrix(1.3333333,0,0,-1.3333333,0,2829.3333)"><g + id="g12" + transform="scale(0.1)"><path + d="m 10622.5,11248.2 c -81.6,-54.3 -178.4,-122.5 -292.3,-197.1 -114.1,-74.3 -241.9,-159.2 -384.07,-247.8 -140.7,-90.6 -296.56,-183.4 -460.04,-283.5 -81.17,-50.9 -167.57,-98.4 -254.29,-149.2 -87.66,-49.5 -174.65,-104.3 -266.68,-153.9 -91.29,-50.7 -184.14,-102.4 -278.09,-154.6 -93.94,-52.3 -191.33,-100.9 -287.65,-153.3 -192.27,-105.6 -393.64,-201 -592.62,-301 -99.96,-49 -201.33,-94.9 -301.6,-142.3 -100.32,-47.3 -200.08,-94.8 -301.41,-136.4 -100.82,-42.9 -200.55,-86.1 -298.87,-129.4 -98.47,-42.8 -198.16,-79 -294.96,-118 -97.38,-37.3 -191.91,-77.8 -286.76,-110.5 -94.68,-33.6 -187.3,-66.4 -277.42,-98.3 -45.11,-15.9 -89.61,-31.7 -133.44,-47.3 -44.37,-13.7 -88.12,-27.1 -131.09,-40.4 -86.05,-26.7 -169.14,-52.4 -248.91,-77.2 -39.96,-12.3 -79.06,-24.4 -117.3,-36.2 -38.71,-9.8 -76.56,-19.5 -113.44,-28.9 -73.75,-19.2 -143.71,-37.5 -209.45,-54.6 -65.9,-16.6 -127.23,-34.6 -184.73,-46.9 -57.61,-11.8 -110.58,-22.6 -158.43,-32.4 -191.41,-40.4 -300.86,-63.6 -301.14,-63.6 0.2,0 111.49,12.4 306.1,34.2 48.79,5.8 102.77,12.2 161.6,19.2 58.83,7.2 121.13,21.7 188.51,33.5 67.27,12.6 138.87,26.1 214.34,40.3 37.73,7.3 76.44,14.7 116.05,22.4 39.11,9.8 79.11,19.8 119.97,29.9 81.75,20.6 166.87,42 255.03,64.2 44.07,11.2 88.95,22.5 134.18,35 44.77,13.9 90.28,28.1 136.37,42.4 92.31,28.7 187.07,58.2 283.99,88.3 48.78,14.2 96.99,31.6 145.43,49.7 48.63,17.8 97.69,35.8 147.18,53.9 98.64,37.1 200.35,71.4 300.78,112.2 100.47,40.8 201.92,82.9 303.87,125.8 102.74,41.4 203.75,88.5 305.43,135.4 101.49,47.1 204.38,92.5 305.51,141.4 200,102.5 402.34,200.4 595.35,308.4 195.7,103 382.34,214.9 563.79,321.6 91.25,52.3 178.28,108.5 264.06,162.3 85.31,54.6 170.2,105.7 249.92,160 80.24,53.6 158.25,105.6 233.75,155.9 75.39,50.4 145.9,102.7 214.81,150.9 138.27,95.7 260.57,190 369.47,272.5 109.4,81.8 200.6,159.2 277.3,220.1 76,61.8 132.8,113.9 172.9,147.7 39.7,34.4 60.8,52.8 60.8,52.8 0,0 -91.6,-64.5 -251.8,-177.2" + style="fill:#668a29;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path14" /><path + d="m 15228.8,15825 c -102.2,-173.3 -176.3,-358 -218.3,-550.5 -19.6,-17.6 -37.4,-37 -53.4,-58 -163.6,59.3 -342.5,63.1 -484.2,31 54.1,715.2 441.9,1360.2 1047.5,1839.3 653.8,517 1560.1,838.1 2563.2,838 v 99.8 c -1024.4,-0.1 -1951.8,-327.4 -2625.1,-859.5 -610.2,-482.2 -1010.9,-1135.1 -1080.6,-1863.1 24.7,-17.7 47.3,-38.1 67.4,-60.7 64.3,-72.4 103.6,-167.4 103.6,-271.7 0,-169.6 -103,-315.2 -250,-377.3 -49.1,-20.7 -102.9,-32.2 -159.4,-32.2 -188.5,0 -346.9,127.6 -394.5,301 -361.9,-310.9 -784.7,-754 -1230.9,-1104.8 -123.3,-97 -259.3,-238.4 -399.4,-403.4 -69.3,-51.5 -131.7,-122.5 -178.1,-219.4 -456.2,-583.6 -916.7,-1325.1 -1094.3,-1547.3 -457.6,-216.4 -911.4,-311.1 -1384.14,-404.1 -205.43,-40.3 -414.41,-80.5 -628.86,-130 -130.39,-30.2 -262.89,-64.1 -397.74,-103.4 -167.58,-48.9 -338.83,-106.5 -514.76,-177.3 -199.85,-80.5 -405.55,-177.8 -618.36,-298.2 -39.92,-22.7 -78.24,-45.2 -117.42,-67.8 -174.61,-100.7 -341.76,-201.1 -501.61,-300.9 C 6201.64,9806 5787.19,9513.4 5425,9237.8 c -215.78,-164.2 -413.55,-322.4 -595.2,-472.1 -135.85,-111.9 -262.73,-218.9 -381.99,-320.3 0.28,0 109.73,23.2 301.14,63.6 47.85,9.8 100.82,20.6 158.43,32.4 57.5,12.3 118.83,30.3 184.73,46.9 65.74,17.1 135.7,35.4 209.45,54.6 36.88,9.4 74.73,19.1 113.44,28.9 38.24,11.8 77.34,23.9 117.3,36.2 79.77,24.8 162.86,50.5 248.91,77.2 42.97,13.3 86.72,26.7 131.09,40.4 43.83,15.6 88.33,31.4 133.44,47.3 90.12,31.9 182.74,64.7 277.42,98.3 94.85,32.7 189.38,73.2 286.76,110.5 96.8,39 196.49,75.2 294.96,118 98.32,43.3 198.05,86.5 298.87,129.4 101.33,41.6 201.09,89.1 301.41,136.4 100.27,47.4 201.64,93.3 301.6,142.3 198.98,100 400.35,195.4 592.62,301 96.32,52.4 193.71,101 287.65,153.3 93.95,52.2 186.8,103.9 278.09,154.6 92.03,49.6 179.02,104.4 266.68,153.9 86.72,50.8 173.12,98.3 254.29,149.2 163.48,100.1 319.34,192.9 460.04,283.5 142.17,88.6 269.97,173.5 384.07,247.8 113.9,74.6 210.7,142.8 292.3,197.1 160.2,112.7 251.8,177.2 251.8,177.2 0,0 -21.1,-18.4 -60.8,-52.8 -40.1,-33.8 -96.9,-85.9 -172.9,-147.7 -76.7,-60.9 -167.9,-138.3 -277.3,-220.1 -108.9,-82.5 -231.2,-176.8 -369.47,-272.5 -68.91,-48.2 -139.42,-100.5 -214.81,-150.9 -75.5,-50.3 -153.51,-102.3 -233.75,-155.9 -79.72,-54.3 -164.61,-105.4 -249.92,-160 -85.78,-53.8 -172.81,-110 -264.06,-162.3 -181.45,-106.7 -368.09,-218.6 -563.79,-321.6 -193.01,-108 -395.35,-205.9 -595.35,-308.4 -101.13,-48.9 -204.02,-94.3 -305.51,-141.4 -101.68,-46.9 -202.69,-94 -305.43,-135.4 -101.95,-42.9 -203.4,-85 -303.87,-125.8 -100.43,-40.8 -202.14,-75.1 -300.78,-112.2 -49.49,-18.1 -98.55,-36.1 -147.18,-53.9 -48.44,-18.1 -96.65,-35.5 -145.43,-49.7 -96.92,-30.1 -191.68,-59.6 -283.99,-88.3 -46.09,-14.3 -91.6,-28.5 -136.37,-42.4 -45.23,-12.5 -90.11,-23.8 -134.18,-35 -88.16,-22.2 -173.28,-43.6 -255.03,-64.2 -40.86,-10.1 -80.86,-20.1 -119.97,-29.9 -39.61,-7.7 -78.32,-15.1 -116.05,-22.4 -75.47,-14.2 -147.07,-27.7 -214.34,-40.3 -67.38,-11.8 -129.68,-26.3 -188.51,-33.5 -58.83,-7 -112.81,-13.4 -161.6,-19.2 -194.61,-21.8 -305.9,-34.2 -306.1,-34.2 -663.4,-564.2 -1086.87,-950 -1454.8,-983.5 72.89,-128.8 730.04,15.1 1664.29,339.7 141.8,49.3 289.85,102.7 443.36,160 51.41,19.1 103.44,38.8 156.02,58.8 52.23,-252.7 180.66,-592.2 394.45,-911.7 142.27,-212.7 291.06,-397.8 427.27,-538.4 l -3.79,-2.3 C 5872.3,6432.5 3914.65,3342.3 4005.23,3206.8 c 90.63,-135.4 2195.32,2735.1 2397.7,2870.4 202.34,135.4 292.97,354.9 202.3,490.5 -12.93,19.3 -29.18,35.6 -47.61,49.4 -63.56,215.9 -210.74,513.2 -415.86,819.8 -123.63,184.9 -164.26,536.4 -199.73,855.9 569.34,235 1177.93,507.5 1784.73,805.2 87.85,-408.4 251.83,-803.5 371.33,-959.7 97.34,-159.8 600.11,-435.7 1207.54,-515.4 253.75,-33.3 490.97,-43.5 686.6,-33.8 l -0.75,-4.3 c -31.64,-241.4 1000.52,-3751 1162.12,-3772.2 161.7,-21.2 -608.5,3453.9 -576.8,3695.4 31.7,241.3 -73.7,454.2 -235.3,475.4 -23.1,3.1 -46,1.6 -68.6,-3.1 -204.1,94.5 -524.66,179.9 -890.44,227.9 -242.62,31.8 -570.78,272.4 -853.59,468.9 40.08,153.4 74.26,327.7 98.67,514 17.73,135 28.87,265.3 33.98,387.3 141.41,76.3 281.53,153.6 419.73,231.8 190.35,107.7 373.91,215.7 551.95,323.5 148.87,90.2 293.4,180.2 433.4,269.7 798.4,510.8 1451.5,1007.5 1939,1436.7 86.8,-272.8 194.7,-506.2 280,-617.7 97.3,-159.7 600.1,-435.6 1207.6,-515.3 253.7,-33.4 490.9,-43.5 686.6,-33.8 l -0.8,-4.4 c -31.7,-241.4 1000.5,-3750.9 1162.2,-3772.1 161.6,-21.2 -608.6,3453.9 -576.9,3695.3 31.7,241.4 -73.7,454.3 -235.3,475.5 -23.1,3 -46,1.5 -68.5,-3.2 -204.2,94.5 -524.8,179.9 -890.5,227.9 -242.6,31.9 -570.8,272.5 -853.6,469 40.1,153.4 74.2,327.7 98.7,513.9 17.4,132.5 28.4,260.4 33.7,380.4 0.9,20.9 1.6,41.5 2.2,61.9 102.2,140.7 160.6,254.7 171.7,334.9 146.6,101.3 290.1,206.2 425.5,312.6 94.5,74.3 186.3,144.3 274.9,210.9 323.5,243.3 601.7,440.3 795.1,626.5 239.4,-336.9 587.3,-500.1 789.4,-364.5 144.8,97.1 192.2,434.7 126.7,751.4 151.3,60.1 258.4,207.6 258.4,380.3 0,226.1 -183.3,409.5 -409.5,409.5 -53.4,0 -104.3,-10.6 -151.1,-29.2 41.3,147 102.8,289 182.7,424.4 236.5,401.5 634.1,745.9 1137.5,989.6 503.4,243.7 1111.9,386.5 1767.9,386.5 v 99.8 c -893.9,-0.1 -1703.5,-259 -2292.2,-680.9 -294.2,-210.9 -533.4,-462.9 -699.2,-744.3" + style="fill:#bfe142;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path16" /><path + d="m 14096.4,15241.1 c -14.7,4.9 -30.1,8.2 -46.6,8.2 -81.6,0 -147.8,-66.2 -147.8,-148 0,-4.7 0.9,-9.3 1.4,-14 7.1,-75 69.6,-133.8 146.4,-133.8 81.7,0 148,66.2 148,147.8 0,65.3 -42.6,120.1 -101.4,139.8" + style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path18" /><path + d="m 14139.5,14802.3 c -153.4,0 -277.7,222.5 -277.7,222.5 0,0 15.3,27.3 41.6,62.5 -0.5,4.7 -1.4,9.3 -1.4,14 0,81.8 66.2,148 147.8,148 16.5,0 31.9,-3.3 46.6,-8.2 14.1,3.6 28.4,6.2 43.1,6.2 153.3,0 277.6,-222.5 277.6,-222.5 0,0 -124.3,-222.5 -277.6,-222.5 z m 238.4,499.7 c -67.2,48.3 -149.3,77.1 -238.4,77.1 -226.1,0 -409.5,-183.4 -409.5,-409.5 0,-37.7 5.5,-73.9 15,-108.5 47.6,-173.4 206,-301 394.5,-301 56.5,0 110.3,11.5 159.4,32.2 147,62.1 250,207.7 250,377.3 0,104.3 -39.3,199.3 -103.6,271.7 -20.1,22.6 -42.7,43 -67.4,60.7" + style="fill:#d4e61d;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path20" /><path + d="m 14139.5,15247.3 c -14.7,0 -29,-2.6 -43.1,-6.2 58.8,-19.7 101.4,-74.5 101.4,-139.8 0,-81.6 -66.3,-147.8 -148,-147.8 -76.8,0 -139.3,58.8 -146.4,133.8 -26.3,-35.2 -41.6,-62.5 -41.6,-62.5 0,0 124.3,-222.5 277.7,-222.5 153.3,0 277.6,222.5 277.6,222.5 0,0 -124.3,222.5 -277.6,222.5" + style="fill:#293519;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path22" /><path + d="m 5843.09,10913.9 1.01,4.3 c 120.16,-58.3 516.88,-510.3 835.31,-813.7 159.85,99.8 327,200.2 501.61,300.9 -364.5,119.2 -1220.16,989.6 -1442,982.7 -18.82,13.2 -39.37,23.6 -61.83,29.8 -157.03,43.9 -337.35,-110.8 -402.78,-345.3 -27.89,-100 -231.91,-1141.6 -444.61,-2306.9 181.65,149.7 379.42,307.9 595.2,472.1 221.99,884.7 395.16,1594 418.09,1676.1" + style="fill:#96d42f;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path24" /><path + d="m 4332.03,5382.5 c 76.92,-21.4 425.39,1249.4 768.63,2579.1 -153.51,-57.3 -301.56,-110.7 -443.36,-160 C 4437.54,6545 4260.27,5402.5 4332.03,5382.5" + style="fill:#96d42f;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path26" /><path + d="m 8577.89,11337.9 2.89,-3.4 c 23.01,-24.8 112.07,-126.4 248.52,-282.4 214.45,49.5 423.43,89.7 628.86,130 -243.75,309.5 -410.74,517.5 -444.37,553.8 -165.47,178.5 -396.56,233.5 -516.09,122.7 -17.15,-15.9 -30.67,-34.5 -41.37,-54.8 -133.2,-63.4 -338.01,-599.7 -539.53,-1032.4 175.93,70.8 347.18,128.4 514.76,177.3 58.36,189 109.38,339.9 146.33,389.2" + style="fill:#96d42f;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path28" /><path + d="m 12455.6,7107.7 c 78.1,72.5 -1347,1953 -2389,3295.7 -140,-89.5 -284.53,-179.5 -433.4,-269.7 1121.5,-1276.9 2742.7,-3099.8 2822.4,-3026" + style="fill:#96d42f;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path30" /><path + d="m 14364.1,11431.3 c 253.8,-33.3 491,-43.5 686.6,-33.8 l -0.7,-4.4 c -31.7,-241.4 1000.4,-3750.9 1162.1,-3772.1 161.7,-21.2 -608.5,3453.9 -576.8,3695.3 31.7,241.4 -73.7,454.3 -235.3,475.5 -23.1,3.1 -46.1,1.6 -68.6,-3.1 -204.2,94.5 -524.7,179.9 -890.5,227.9 -242.6,31.9 -570.8,272.5 -853.6,469 40.1,153.3 74.3,327.7 98.7,513.9 27.3,207.8 39,404.5 37.1,576.4 -88.6,-66.6 -180.4,-136.6 -274.9,-210.9 -135.4,-106.4 -278.9,-211.3 -425.5,-312.6 -11.1,-80.2 -69.5,-194.2 -171.7,-334.9 -0.6,-20.4 -1.3,-41 -2.2,-61.9 90.5,-311.4 213.1,-585.2 307.7,-709 97.4,-159.7 600.2,-435.6 1207.6,-515.3" + style="fill:#96d42f;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path32" /></g></g></svg>