Skip to content
This repository was archived by the owner on Dec 2, 2022. It is now read-only.

WIP Add support for affiliate campaigns #109

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class Config {

// optional pricing config
pricingPlans?: PricingPlan[]
coupons?: Coupon[]

// optional version info
saasifyVersion?: number = 1
Expand Down Expand Up @@ -165,14 +164,6 @@ Optional array of `PricingPlan` objects to fully customize the pricing of your p

Refer to our [pricing guide](pricing.md) for details on pricing configuration including examples of setting up common billing models.

#### coupons

Optional array of `Coupon` objects to enable for this product.

Coupons are a great way to offer discounts to different customer segments.

Saasify coupons follow the same format as [Stripe coupons](https://stripe.com/docs/billing/subscriptions/discounts ':target=_blank').

#### saasifyVersion

Optional `string` platform version. The only allowable value is currently `1`.
Expand Down
22 changes: 7 additions & 15 deletions docs/pricing.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class Config {

// optional pricing config
pricingPlans?: PricingPlan[]
coupons?: Coupon[]
enableCoupons?: boolean
}

class PricingPlan {
Expand Down Expand Up @@ -125,20 +125,6 @@ class PricingPlanTier {
upTo: string
}

class Coupon {
name?: string

currency?: string
amount_off?: number
percent_off?: number

duration: string
duration_in_months?: number

redeem_by?: string
max_redemptions?: number
}

class RateLimit {
// whether or not this rate limit is enabled
enabled?: boolean = true
Expand All @@ -155,6 +141,12 @@ Saasify's pricing plan schemas are based directly on Stripe's subscription billi

Note that Saasify uses camelCase for property names whereas Stripe uses snake_case. Converting between the two should be straightforward, but please let us know if you have any questions.

## Coupons

To enable coupons / discounts / promo codes for your product, set `enableCoupons` to `true` in your config and then [create your coupons in your Stripe dashboard](https://stripe.com/docs/billing/subscriptions/discounts).

Saasify will check for verify that a given coupon code exists and is valid on your Stripe account before applying it to a customer subscription.

<p align="center">
<img src="./_media/undraw/stripe_payments.svg" alt="Stripe" width="200" />
</p>
1 change: 0 additions & 1 deletion examples/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

These examples show how to use different Saasify platform features.

- [coupons](./typescript/coupons) - Shows the use of coupons.
- [secrets](./typescript/secrets) - Shows the use of environment variable secrets.
- [features](./typescript/features) - Shows how to customize the SaaS client features section.

Expand Down
66 changes: 0 additions & 66 deletions examples/typescript/coupons/.gitignore

This file was deleted.

3 changes: 0 additions & 3 deletions examples/typescript/coupons/index.ts

This file was deleted.

9 changes: 0 additions & 9 deletions examples/typescript/coupons/readme.md

This file was deleted.

14 changes: 0 additions & 14 deletions examples/typescript/coupons/saasify.json

This file was deleted.

1 change: 1 addition & 0 deletions packages/react-saasify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"saasify-client": "^1.20.0",
"saasify-codegen": "^1.20.0",
"saasify-faas-sdk": "^1.20.0",
"stripe-coupon-to-string": "^0.1.2",
"titleize": "^2.1.0"
},
"peerDependencies": {
Expand Down
36 changes: 34 additions & 2 deletions packages/react-saasify/src/components/CheckoutForm/CheckoutForm.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import theme from 'lib/theme'
import stripeCouponToString from 'stripe-coupon-to-string'

import {
StripeProvider,
Expand All @@ -12,6 +13,7 @@ import {
import { observer, inject } from 'mobx-react'
import { Button, Icon, Tooltip } from 'lib/antd'
import env from 'lib/env'
import API from 'lib/api'

import styles from './styles.module.css'

Expand All @@ -33,6 +35,7 @@ const createOptions = (fontSize = 16) => {
}
}

@inject('auth')
@inject('config')
@observer
export class CheckoutForm extends Component {
Expand All @@ -43,6 +46,7 @@ export class CheckoutForm extends Component {
action: PropTypes.node,
footer: PropTypes.node,
className: PropTypes.string,
auth: PropTypes.object.isRequired,
config: PropTypes.object.isRequired
}

Expand Down Expand Up @@ -74,8 +78,13 @@ class CheckoutFormImpl extends Component {
loading: false
}

state = {
coupon: null
}

render() {
const { config, loading, title, action, footer, className } = this.props
const { coupon } = this.state

return (
<form
Expand Down Expand Up @@ -106,18 +115,28 @@ class CheckoutFormImpl extends Component {
<CardElement {...createOptions()} />
</label>

{config.coupons && config.coupons.length > 0 && (
{config?.deployment?.enableCoupons && (
<label className={theme(styles, 'label')}>
Promo Code
<input
className={theme(styles, 'input')}
name='coupon'
type='text'
placeholder=''
placeholder='Optional'
onChange={this._onChangeCoupon}
/>
</label>
)}

{coupon &&
(coupon.valid ? (
<p>
<i>{stripeCouponToString(coupon)}</i>
</p>
) : (
<p>This coupon is no longer valid.</p>
))}

{action && (
<Button
type='primary'
Expand All @@ -141,4 +160,17 @@ class CheckoutFormImpl extends Component {
const coupon = e.target.coupon && e.target.coupon.value
this.props.onSubmit({ name, coupon, stripe: this.props.stripe })
}

_onChangeCoupon = async (e) => {
const consumerId = this.props.auth.consumer?.id
const couponId = e.target.value

try {
const coupon = await API.getCouponForConsumer(consumerId, couponId)
this.setState({ coupon })
} catch (err) {
console.error(err)
this.setState({ coupon: null })
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
.actions {
width: 100%;
margin-top: 0.5em;
flex-wrap: wrap;
}

.actions .action {
Expand Down
Loading