roam/aweber/20210302135423-stripe_api.org
2021-09-01 16:57:39 -04:00

1166 lines
30 KiB
Org Mode

:PROPERTIES:
:ID: 7d940785-68b9-4da7-bad1-4771d496168c
:END:
#+title: Stripe API
#+PROPERTY: header-args:shell :results output
#+PROPERTY: header-args :cache yes :exports both :var account="acct_1IGnMkIoFf3wvXpR" :eval no-export
#+TOC: headlines 2
* Product
#+name: product
#+begin_src shell :results output
stripe products create \
--stripe-account=$account \
--name="Example Product (Org-Roam Doc)" \
-d "images[0]"="https://www.orgroam.com/img/logo.png" \
| jq -r .id
#+end_src
#+RESULTS[0094337876c9d1d55bdfd716fda03da4d71e6e9c]: product
: prod_JBeWfGHNqBcy5B
** Non-Recurring Price
#+name: price-single
#+begin_src shell :var product=product
stripe prices create \
--stripe-account=$account \
--product=$product \
--unit-amount=-1 \
--currency=jpy \
-d "nickname"="Non-Recurring"
#+end_src
#+RESULTS[d9b883e9f85ada78e689aa559e5488f41118026c]: price-single
: {
: "error": {
: "message": "This value must be less than or equal to 999999999999 (it currently is '9999999999991').",
: "param": "unit_amount",
: "type": "invalid_request_error"
: }
: }
** Recurring Price
#+name: price-recurring
#+begin_src shell :var product=product
stripe prices create \
--stripe-account=$account \
--product=$product \
--unit-amount=999 \
--currency=usd \
-d "nickname"="Recurring" \
-d "recurring[interval]"="month" \
| jq -r .id
#+end_src
#+RESULTS[8e8d2f69d8521128b6f69143d933ac173a9d9433]: price-recurring
: price_1IZHFZIoFf3wvXpRtHW5EKFo
* Customer
#+name: customer
#+begin_src shell :results output
stripe customers create \
--stripe-account=$account \
--description="Org Roam Docs Customer" \
--name="Correl Roush" \
--email="correl+stripe.roam.docs@gmail.com" \
| jq -r .id
#+end_src
#+RESULTS[c85b560e232abaac68423ce28b71029a7146c021]: customer
: cus_JBeXOsWSmfXrDX
* One-Time Invoicing
We set up a purchase for the client to then complete by confirming the payment
intent with payment information.
** Create invoice item
#+NAME: otp-invoiceitem
#+HEADER: :var customer=customer
#+HEADER: :var price=price-single
#+begin_src shell
stripe invoiceitems create \
--stripe-account=$account \
--customer=$customer \
--price=$price \
| jq -r .id
#+end_src
#+RESULTS[a475da86ff77f519ee5bd8b928377c88b696abb8]: otp-invoiceitem
: ii_1IZHGHIoFf3wvXpRnP7xBsbd
** Create invoice
#+NAME: otp-invoice
#+HEADER: :var customer=customer
#+begin_src shell
stripe invoices create \
--stripe-account="$account" \
--customer="$customer" \
| jq -r .id
#+end_src
#+RESULTS[12e688bd6def0039295ea18f1a38482c12c2a9ad]: otp-invoice
: in_1IZHGOIoFf3wvXpRuP2Og77y
#+NAME: otp-intent
#+HEADER: :var invoice=otp-invoice
#+begin_src shell
stripe invoices finalize_invoice \
--stripe-account=$account \
$invoice
#+end_src
#+RESULTS[e5aeed338fa41b3b018af508fee5eae83942a255]: otp-intent
#+begin_example
{
"id": "in_1IZHGOIoFf3wvXpRuP2Og77y",
"object": "invoice",
"account_country": "US",
"account_name": "Correl's Stuff",
"account_tax_ids": null,
"amount_due": 350,
"amount_paid": 0,
"amount_remaining": 350,
"application_fee_amount": null,
"attempt_count": 0,
"attempted": false,
"auto_advance": false,
"billing_reason": "manual",
"charge": null,
"collection_method": "charge_automatically",
"created": 1616771272,
"currency": "usd",
"custom_fields": null,
"customer": "cus_JBeXOsWSmfXrDX",
"customer_address": null,
"customer_email": "correl+stripe.roam.docs@gmail.com",
"customer_name": "Correl Roush",
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [
],
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [
],
"description": null,
"discount": null,
"discounts": [
],
"due_date": null,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_1IGnMkIoFf3wvXpR/invst_JBeZpwIUNWNhG3JLRP5QBZ2RpKeDs8t",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_1IGnMkIoFf3wvXpR/invst_JBeZpwIUNWNhG3JLRP5QBZ2RpKeDs8t/pdf",
"last_finalization_error": null,
"lines": {
"object": "list",
"data": [
{
"id": "il_1IZHGHIoFf3wvXpRMwwv5nhW",
"object": "line_item",
"amount": 350,
"currency": "usd",
"description": "Example Product (Org-Roam Doc)",
"discount_amounts": [
],
"discountable": true,
"discounts": [
],
"invoice_item": "ii_1IZHGHIoFf3wvXpRnP7xBsbd",
"livemode": false,
"metadata": {
},
"period": {
"end": 1616771265,
"start": 1616771265
},
"plan": null,
"price": {
"id": "price_1IZHFSIoFf3wvXpRHwH3GRm8",
"object": "price",
"active": true,
"billing_scheme": "per_unit",
"created": 1616771214,
"currency": "usd",
"livemode": false,
"lookup_key": null,
"metadata": {
},
"nickname": "Non-Recurring",
"product": "prod_JBeWfGHNqBcy5B",
"recurring": null,
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 350,
"unit_amount_decimal": "350"
},
"proration": false,
"quantity": 1,
"subscription": null,
"tax_amounts": [
],
"tax_rates": [
],
"type": "invoiceitem"
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/invoices/in_1IZHGOIoFf3wvXpRuP2Og77y/lines"
},
"livemode": false,
"metadata": {
},
"next_payment_attempt": null,
"number": "0EBAE885-0001",
"on_behalf_of": null,
"paid": false,
"payment_intent": "pi_1IZHH8IoFf3wvXpRKYVCxVtO",
"payment_settings": {
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1616771272,
"period_start": 1616771272,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": null,
"status": "open",
"status_transitions": {
"finalized_at": 1616771318,
"marked_uncollectible_at": null,
"paid_at": null,
"voided_at": null
},
"subscription": null,
"subtotal": 350,
"tax": null,
"total": 350,
"total_discount_amounts": [
],
"total_tax_amounts": [
],
"transfer_data": null,
"webhooks_delivered_at": 1616771274
}
#+end_example
** Pay the invoice
#+HEADER: :var intent="pi_1IZG0gIoFf3wvXpRMv4JIJlv"
#+begin_src shell
stripe payment_intents confirm $intent \
--payment-method=pm_card_visa
#+end_src
#+RESULTS[2cd37055813d6711442f7462d1d7e6338dce3ab7]:
#+begin_example
{
"id": "pi_1IZG0gIoFf3wvXpRMv4JIJlv",
"object": "payment_intent",
"amount": 350,
"amount_capturable": 0,
"amount_received": 350,
"application": null,
"application_fee_amount": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"object": "list",
"data": [
{
"id": "ch_1IZG1rIoFf3wvXpRVFw1lFw7",
"object": "charge",
"amount": 350,
"amount_captured": 350,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_1IZG1rIoFf3wvXpR7ROwaORh",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"calculated_statement_descriptor": "CORRELS STUFF",
"captured": true,
"created": 1616766527,
"currency": "usd",
"customer": "cus_JBNQ85gmNWdWB3",
"description": "Payment for Invoice",
"destination": null,
"dispute": null,
"disputed": false,
"failure_code": null,
"failure_message": null,
"fraud_details": {
},
"invoice": "in_1IZG0YIoFf3wvXpR6aGs4GtD",
"livemode": false,
"metadata": {
},
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 39,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_1IZG0gIoFf3wvXpRMv4JIJlv",
"payment_method": "pm_1IZG1qIoFf3wvXpRX3NUaXbF",
"payment_method_details": {
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": null
},
"country": "US",
"exp_month": 3,
"exp_year": 2022,
"fingerprint": "ueUYSX2U2UUDmYpO",
"funding": "credit",
"installments": null,
"last4": "4242",
"network": "visa",
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": null,
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/acct_1IGnMkIoFf3wvXpR/ch_1IZG1rIoFf3wvXpRVFw1lFw7/rcpt_JBdIhBejsEXLYLJMLjteIt8UWMMh5md",
"refunded": false,
"refunds": {
"object": "list",
"data": [
],
"has_more": false,
"total_count": 0,
"url": "/v1/charges/ch_1IZG1rIoFf3wvXpRVFw1lFw7/refunds"
},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_1IZG0gIoFf3wvXpRMv4JIJlv"
},
"client_secret": "pi_1IZG0gIoFf3wvXpRMv4JIJlv_secret_48XYkW8i6bDhBOtA7AE6R9drd",
"confirmation_method": "automatic",
"created": 1616766454,
"currency": "usd",
"customer": "cus_JBNQ85gmNWdWB3",
"description": "Payment for Invoice",
"invoice": "in_1IZG0YIoFf3wvXpR6aGs4GtD",
"last_payment_error": null,
"livemode": false,
"metadata": {
},
"next_action": null,
"on_behalf_of": null,
"payment_method": "pm_1IZG1qIoFf3wvXpRX3NUaXbF",
"payment_method_options": {
"card": {
"installments": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"receipt_email": null,
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
#+end_example
* One-Time Invoicing (Payment method first)
By having the client prepare the payment intent before requesting the purchase,
we can handle invoicing and completing the purchase on our end without further
input from the client.
** Create a payment method
#+NAME: payment-method
#+begin_src shell
stripe payment_methods create \
--stripe-account=$account \
--type=card \
-d "card[number]"=4242424242424242 \
-d "card[exp_month]"=3 \
-d "card[exp_year]"=2022 \
-d "card[cvc]"=314 \
| jq -r '.id'
#+end_src
#+RESULTS[df876e7c97be477440f418bec23e78b376d86944]: payment-method
: pm_1J5FujIoFf3wvXpRgemyYO93
** Attach the payment method to the customer
#+HEADER: :var customer=customer
#+HEADER: :var payment_method=payment-method
#+begin_src shell
stripe payment_methods attach "$payment_method" \
--stripe-account=$account \
--customer="$customer"
#+end_src
#+RESULTS[4290d9f7fe1f6c7362c61268ca3da026798046d0]:
#+begin_example
{
"id": "pm_1IZHILIoFf3wvXpR3oRjPoMx",
"object": "payment_method",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2022,
"fingerprint": "C5qD8oSCGvVCbtcH",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1616771394,
"customer": "cus_JBeXOsWSmfXrDX",
"livemode": false,
"metadata": {
},
"type": "card"
}
#+end_example
** Create invoice item
#+NAME: otp2-invoiceitem
#+HEADER: :var customer=customer
#+HEADER: :var price=price-single
#+begin_src shell
stripe invoiceitems create \
--stripe-account=$account \
--customer=$customer \
--price=$price \
| jq -r .id
#+end_src
#+RESULTS[a475da86ff77f519ee5bd8b928377c88b696abb8]: otp2-invoiceitem
: ii_1IZJF0IoFf3wvXpROwCaDRo0
** Create invoice
#+NAME: otp2-invoice
#+HEADER: :var customer=customer
#+HEADER: :var payment_method="pm_1IZHILIoFf3wvXpR3oRjPoMx"
#+begin_src shell
stripe invoices create \
--stripe-account="$account" \
--customer="$customer" \
--auto-advance=false \
-d "application_fee_amount"=35 \
-d "default_payment_method"=$payment_method \
| jq -r .id
#+end_src
#+RESULTS[aa892f23ee3bedd4ce813b435a49ef440a64b3ed]: otp2-invoice
: in_1IZJF5IoFf3wvXpRu9PnTZIl
** Pay Invoice
#+NAME: otp2-payment
#+HEADER: :var invoice=otp2-invoice
#+begin_src shell
stripe invoices pay \
--stripe-account=$account \
$invoice
#+end_src
#+RESULTS[269ab4684e04178d7081e0d614e579f925369798]: otp2-payment
#+begin_example
{
"id": "in_1IZJF5IoFf3wvXpRu9PnTZIl",
"object": "invoice",
"account_country": "US",
"account_name": "Correl's Stuff",
"account_tax_ids": null,
"amount_due": 350,
"amount_paid": 350,
"amount_remaining": 0,
"application_fee_amount": 35,
"attempt_count": 1,
"attempted": true,
"auto_advance": false,
"billing_reason": "manual",
"charge": "ch_1IZJFdIoFf3wvXpRfCT89Njs",
"collection_method": "charge_automatically",
"created": 1616778879,
"currency": "usd",
"custom_fields": null,
"customer": "cus_JBeXOsWSmfXrDX",
"customer_address": null,
"customer_email": "correl+stripe.roam.docs@gmail.com",
"customer_name": "Correl Roush",
"customer_phone": null,
"customer_shipping": null,
"customer_tax_exempt": "none",
"customer_tax_ids": [
],
"default_payment_method": "pm_1IZHILIoFf3wvXpR3oRjPoMx",
"default_source": null,
"default_tax_rates": [
],
"description": null,
"discount": null,
"discounts": [
],
"due_date": null,
"ending_balance": 0,
"footer": null,
"hosted_invoice_url": "https://invoice.stripe.com/i/acct_1IGnMkIoFf3wvXpR/invst_JBgcIFGvkWp3ZnI1HxNWwAZl7ccRzbY",
"invoice_pdf": "https://pay.stripe.com/invoice/acct_1IGnMkIoFf3wvXpR/invst_JBgcIFGvkWp3ZnI1HxNWwAZl7ccRzbY/pdf",
"last_finalization_error": null,
"lines": {
"object": "list",
"data": [
{
"id": "il_1IZJF0IoFf3wvXpRdhrY6NFl",
"object": "line_item",
"amount": 350,
"currency": "usd",
"description": "Example Product (Org-Roam Doc)",
"discount_amounts": [
],
"discountable": true,
"discounts": [
],
"invoice_item": "ii_1IZJF0IoFf3wvXpROwCaDRo0",
"livemode": false,
"metadata": {
},
"period": {
"end": 1616778874,
"start": 1616778874
},
"plan": null,
"price": {
"id": "price_1IZHFSIoFf3wvXpRHwH3GRm8",
"object": "price",
"active": true,
"billing_scheme": "per_unit",
"created": 1616771214,
"currency": "usd",
"livemode": false,
"lookup_key": null,
"metadata": {
},
"nickname": "Non-Recurring",
"product": "prod_JBeWfGHNqBcy5B",
"recurring": null,
"tiers_mode": null,
"transform_quantity": null,
"type": "one_time",
"unit_amount": 350,
"unit_amount_decimal": "350"
},
"proration": false,
"quantity": 1,
"subscription": null,
"tax_amounts": [
],
"tax_rates": [
],
"type": "invoiceitem"
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/invoices/in_1IZJF5IoFf3wvXpRu9PnTZIl/lines"
},
"livemode": false,
"metadata": {
},
"next_payment_attempt": null,
"number": "0EBAE885-0013",
"on_behalf_of": null,
"paid": true,
"payment_intent": "pi_1IZJFcIoFf3wvXpR60FJfbvD",
"payment_settings": {
"payment_method_options": null,
"payment_method_types": null
},
"period_end": 1619449837,
"period_start": 1616771437,
"post_payment_credit_notes_amount": 0,
"pre_payment_credit_notes_amount": 0,
"receipt_number": null,
"starting_balance": 0,
"statement_descriptor": null,
"status": "paid",
"status_transitions": {
"finalized_at": 1616778912,
"marked_uncollectible_at": null,
"paid_at": 1616778912,
"voided_at": null
},
"subscription": null,
"subtotal": 350,
"tax": null,
"total": 350,
"total_discount_amounts": [
],
"total_tax_amounts": [
],
"transfer_data": null,
"webhooks_delivered_at": 1616778880
}
#+end_example
** Error handling
- Bad payment methods will fail to attach to the customer, stopping the purchase
flow before we attempt to set up the invoice.
- Failure to create an invoice leaves the invoice item on the customer. Because
we do not re-use customers, this will not be invoiced and charged.
- Invoice creation leaves the invoice in a draft state, from which it will not
progress without an action being taken.
- Payment failure results in the invoice being in a failed state, and it will
not retry payment.
- Customer can be cleaned up at any point, which will clean up all data
pertaining to it (invoice items, draft / failed invoices).
*** Attempting to attach a test payment method to a live customer
#+HEADER: :var customer="cus_JihU7tb4eVdeda"
#+HEADER: :var payment_method=payment-method
#+begin_src shell
stripe payment_methods attach "$payment_method" \
--live -p correl \
--customer="$customer"
#+end_src
#+RESULTS[e968f28eb95b5cc597fe402343d675f1fb5713de]:
: {
: "error": {
: "code": "resource_missing",
: "doc_url": "https://stripe.com/docs/error-codes/resource-missing",
: "message": "No such paymentmethod: 'pm_1J5FujIoFf3wvXpRgemyYO93'; a similar object exists in test mode, but a live mode key was used to make this request.",
: "param": "payment_method",
: "type": "invalid_request_error"
: }
: }
* Subscriptions
Subscriptions require that a customer and payment method are already available.
This means we'll need to have the client prepare a payment method, then pass
that to us to be attached to the customer we create. We'll then be able to build
the subscription, and the payment will be processed by Stripe.
** Create a payment method
#+NAME: payment-method
#+begin_src shell
stripe payment_methods create \
--stripe-account=$account \
--type=card \
-d "card[number]"=4242424242424242 \
-d "card[exp_month]"=3 \
-d "card[exp_year]"=2022 \
-d "card[cvc]"=314 \
| jq -r '.id'
#+end_src
#+RESULTS[df876e7c97be477440f418bec23e78b376d86944]: payment-method
: pm_1IZHILIoFf3wvXpR3oRjPoMx
** Attach the payment method to the customer
#+HEADER: :var customer=customer
#+HEADER: :var payment_method=payment-method
#+begin_src shell
stripe payment_methods attach "$payment_method" \
--stripe-account=$account \
--customer="$customer"
#+end_src
#+RESULTS[4290d9f7fe1f6c7362c61268ca3da026798046d0]:
#+begin_example
{
"id": "pm_1IZHILIoFf3wvXpR3oRjPoMx",
"object": "payment_method",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 3,
"exp_year": 2022,
"fingerprint": "C5qD8oSCGvVCbtcH",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1616771394,
"customer": "cus_JBeXOsWSmfXrDX",
"livemode": false,
"metadata": {
},
"type": "card"
}
#+end_example
** Set the payment method as the default
#+HEADER: :var customer=customer
#+HEADER: :var payment_method=payment-method
#+begin_src shell
stripe customers update "$customer" \
--stripe-account=$account \
-d "invoice_settings[default_payment_method]=$payment_method"
#+end_src
#+RESULTS[be0aa9707c8abf6bb987623eb11acdc96f3931d6]:
#+begin_example
{
"id": "cus_JBeXOsWSmfXrDX",
"object": "customer",
"address": null,
"balance": 0,
"created": 1616771128,
"currency": "usd",
"default_source": null,
"delinquent": false,
"description": "Org Roam Docs Customer",
"discount": null,
"email": "correl+stripe.roam.docs@gmail.com",
"invoice_prefix": "0EBAE885",
"invoice_settings": {
"custom_fields": null,
"default_payment_method": "pm_1IZHILIoFf3wvXpR3oRjPoMx",
"footer": null
},
"livemode": false,
"metadata": {
},
"name": "Correl Roush",
"next_invoice_sequence": 2,
"phone": null,
"preferred_locales": [
],
"shipping": null,
"tax_exempt": "none"
}
#+end_example
** Create subscription
#+HEADER: :var customer=customer
#+HEADER: :var price=price-recurring
#+begin_src shell
stripe subscriptions create \
--stripe-account=$account \
--customer="$customer" \
-d "items[0][price]"=$price
#+end_src
#+RESULTS[a3e938342dd705eb0de47989fb744ef0756905ea]:
#+begin_example
{
"id": "sub_JBec2LL8kvDiPi",
"object": "subscription",
"application_fee_percent": null,
"billing_cycle_anchor": 1616771437,
"billing_thresholds": null,
"cancel_at": null,
"cancel_at_period_end": false,
"canceled_at": null,
"collection_method": "charge_automatically",
"created": 1616771437,
"current_period_end": 1619449837,
"current_period_start": 1616771437,
"customer": "cus_JBeXOsWSmfXrDX",
"days_until_due": null,
"default_payment_method": null,
"default_source": null,
"default_tax_rates": [
],
"discount": null,
"ended_at": null,
"items": {
"object": "list",
"data": [
{
"id": "si_JBecQCXkjKpi2j",
"object": "subscription_item",
"billing_thresholds": null,
"created": 1616771438,
"metadata": {
},
"plan": {
"id": "price_1IZHFZIoFf3wvXpRtHW5EKFo",
"object": "plan",
"active": true,
"aggregate_usage": null,
"amount": 999,
"amount_decimal": "999",
"billing_scheme": "per_unit",
"created": 1616771221,
"currency": "usd",
"interval": "month",
"interval_count": 1,
"livemode": false,
"metadata": {
},
"nickname": "Recurring",
"product": "prod_JBeWfGHNqBcy5B",
"tiers_mode": null,
"transform_usage": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"price": {
"id": "price_1IZHFZIoFf3wvXpRtHW5EKFo",
"object": "price",
"active": true,
"billing_scheme": "per_unit",
"created": 1616771221,
"currency": "usd",
"livemode": false,
"lookup_key": null,
"metadata": {
},
"nickname": "Recurring",
"product": "prod_JBeWfGHNqBcy5B",
"recurring": {
"aggregate_usage": null,
"interval": "month",
"interval_count": 1,
"trial_period_days": null,
"usage_type": "licensed"
},
"tiers_mode": null,
"transform_quantity": null,
"type": "recurring",
"unit_amount": 999,
"unit_amount_decimal": "999"
},
"quantity": 1,
"subscription": "sub_JBec2LL8kvDiPi",
"tax_rates": [
]
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/subscription_items?subscription=sub_JBec2LL8kvDiPi"
},
"latest_invoice": "in_1IZHJ3IoFf3wvXpR6u3Yylyu",
"livemode": false,
"metadata": {
},
"next_pending_invoice_item_invoice": null,
"pause_collection": null,
"pending_invoice_item_interval": null,
"pending_setup_intent": null,
"pending_update": null,
"plan": {
"id": "price_1IZHFZIoFf3wvXpRtHW5EKFo",
"object": "plan",
"active": true,
"aggregate_usage": null,
"amount": 999,
"amount_decimal": "999",
"billing_scheme": "per_unit",
"created": 1616771221,
"currency": "usd",
"interval": "month",
"interval_count": 1,
"livemode": false,
"metadata": {
},
"nickname": "Recurring",
"product": "prod_JBeWfGHNqBcy5B",
"tiers_mode": null,
"transform_usage": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"quantity": 1,
"schedule": null,
"start_date": 1616771437,
"status": "active",
"transfer_data": null,
"trial_end": null,
"trial_start": null
}
#+end_example
** Failure cases
Stripe provides a [[https://stripe.com/docs/testing#cards-responses][set of credit card numbers]] for eliciting various responses
from their API.
*** Charge failure
#+HEADER: :var customer=customer
#+HEADER: :var price=price-recurring
#+begin_src shell
payment_method=$(stripe payment_methods create \
--stripe-account=$account \
--type=card \
-d "card[number]"=4000000000000341 \
-d "card[exp_month]"=3 \
-d "card[exp_year]"=2022 \
-d "card[cvc]"=314 \
| jq -r '.id')
stripe payment_methods attach "$payment_method" \
--stripe-account=$account \
--customer="$customer" \
>/dev/null
# stripe customers update "$customer" \
# --stripe-account=$account \
# -d "invoice_settings[default_payment_method]=$payment_method" >/dev/null
stripe subscriptions create \
--stripe-account=$account \
--customer="$customer" \
-d "default_payment_method"=$payment_method \
-d "items[0][price]"=$price
#+end_src
#+RESULTS[7065d76c5fb4d771b666fc2b39b57cc589255de7]:
#+begin_example
{
"id": "sub_JgoaKuIlHIVW7l",
"object": "subscription",
"application_fee_percent": null,
"automatic_tax": {
"enabled": false
},
"billing_cycle_anchor": 1623958380,
"billing_thresholds": null,
"cancel_at": null,
"cancel_at_period_end": false,
"canceled_at": null,
"collection_method": "charge_automatically",
"created": 1623958380,
"current_period_end": 1626550380,
"current_period_start": 1623958380,
"customer": "cus_JBeXOsWSmfXrDX",
"days_until_due": null,
"default_payment_method": "pm_1J3QxSIoFf3wvXpRCCwKw9Cj",
"default_source": null,
"default_tax_rates": [
],
"discount": null,
"ended_at": null,
"items": {
"object": "list",
"data": [
{
"id": "si_Jgoa4zrlQvwRbA",
"object": "subscription_item",
"billing_thresholds": null,
"created": 1623958381,
"metadata": {
},
"plan": {
"id": "price_1IZHFZIoFf3wvXpRtHW5EKFo",
"object": "plan",
"active": true,
"aggregate_usage": null,
"amount": 999,
"amount_decimal": "999",
"billing_scheme": "per_unit",
"created": 1616771221,
"currency": "usd",
"interval": "month",
"interval_count": 1,
"livemode": false,
"metadata": {
},
"nickname": "Recurring",
"product": "prod_JBeWfGHNqBcy5B",
"tiers_mode": null,
"transform_usage": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"price": {
"id": "price_1IZHFZIoFf3wvXpRtHW5EKFo",
"object": "price",
"active": true,
"billing_scheme": "per_unit",
"created": 1616771221,
"currency": "usd",
"livemode": false,
"lookup_key": null,
"metadata": {
},
"nickname": "Recurring",
"product": "prod_JBeWfGHNqBcy5B",
"recurring": {
"aggregate_usage": null,
"interval": "month",
"interval_count": 1,
"trial_period_days": null,
"usage_type": "licensed"
},
"tiers_mode": null,
"transform_quantity": null,
"type": "recurring",
"unit_amount": 999,
"unit_amount_decimal": "999"
},
"quantity": 1,
"subscription": "sub_JgoaKuIlHIVW7l",
"tax_rates": [
]
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/subscription_items?subscription=sub_JgoaKuIlHIVW7l"
},
"latest_invoice": "in_1J3QxUIoFf3wvXpRyWAuAxGq",
"livemode": false,
"metadata": {
},
"next_pending_invoice_item_invoice": null,
"pause_collection": null,
"pending_invoice_item_interval": null,
"pending_setup_intent": null,
"pending_update": null,
"plan": {
"id": "price_1IZHFZIoFf3wvXpRtHW5EKFo",
"object": "plan",
"active": true,
"aggregate_usage": null,
"amount": 999,
"amount_decimal": "999",
"billing_scheme": "per_unit",
"created": 1616771221,
"currency": "usd",
"interval": "month",
"interval_count": 1,
"livemode": false,
"metadata": {
},
"nickname": "Recurring",
"product": "prod_JBeWfGHNqBcy5B",
"tiers_mode": null,
"transform_usage": null,
"trial_period_days": null,
"usage_type": "licensed"
},
"quantity": 1,
"schedule": null,
"start_date": 1623958380,
"status": "incomplete",
"transfer_data": null,
"trial_end": null,
"trial_start": null
}
#+end_example
* A Unified Purchasing Experience
Both [[One-Time Invoicing (Payment method first)]] and [[Subscriptions]] require a
payment intent be prepared by the client and to be provided to our backend along
with customer information.