1167 lines
30 KiB
Org Mode
1167 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.
|